Make pane expansion public Relnote: Introduce pane expansion APIs to public Test: existing test Bug: 327637983 Change-Id: I301d6a9efc34f352707aaabe45dca05ed40a9f97
diff --git a/compose/material3/adaptive/adaptive-layout/api/current.txt b/compose/material3/adaptive/adaptive-layout/api/current.txt index 1305c09..c69428b 100644 --- a/compose/material3/adaptive/adaptive-layout/api/current.txt +++ b/compose/material3/adaptive/adaptive-layout/api/current.txt
@@ -36,7 +36,7 @@ public final class ListDetailPaneScaffoldKt { method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane); - method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane); + method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState); } public final class ListDetailPaneScaffoldRole { @@ -60,6 +60,39 @@ property public final String Hidden; } + @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public abstract sealed class PaneExpansionAnchor { + } + + public static final class PaneExpansionAnchor.Offset extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor { + ctor public PaneExpansionAnchor.Offset(float offset); + method public float getOffset(); + property public final float offset; + } + + public static final class PaneExpansionAnchor.Proportion extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor { + ctor public PaneExpansionAnchor.Proportion(@FloatRange(from=0.0, to=1.0) float proportion); + method public float getProportion(); + property public final float proportion; + } + + public final class PaneExpansionDragHandleKt { + method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void PaneExpansionDragHandle(androidx.compose.material3.adaptive.layout.PaneExpansionState state, long color, optional androidx.compose.ui.Modifier modifier); + } + + @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public final class PaneExpansionState implements androidx.compose.foundation.gestures.DraggableState { + method public void clear(); + method public void dispatchRawDelta(float delta); + method public suspend Object? drag(androidx.compose.foundation.MutatePriority dragPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.DragScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>); + method public boolean isUnspecified(); + method public void setFirstPaneProportion(@FloatRange(from=0.0, to=1.0) float firstPaneProportion); + method public void setFirstPaneWidth(int firstPaneWidth); + field public static final androidx.compose.material3.adaptive.layout.PaneExpansionState.Companion Companion; + field public static final int Unspecified = -1; // 0xffffffff + } + + public static final class PaneExpansionState.Companion { + } + @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public sealed interface PaneExpansionStateKey { field public static final androidx.compose.material3.adaptive.layout.PaneExpansionStateKey.Companion Companion; } @@ -74,6 +107,11 @@ property public abstract androidx.compose.material3.adaptive.layout.PaneExpansionStateKey paneExpansionStateKey; } + public final class PaneExpansionStateKt { + method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.PaneExpansionState rememberPaneExpansionState(optional androidx.compose.material3.adaptive.layout.PaneExpansionStateKey key, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor> anchors); + method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.PaneExpansionState rememberPaneExpansionState(androidx.compose.material3.adaptive.layout.PaneExpansionStateKeyProvider keyProvider, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor> anchors); + } + public final class PaneKt { method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void AnimatedPane(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.AnimatedPaneScope,kotlin.Unit> content); } @@ -117,7 +155,7 @@ public final class SupportingPaneScaffoldKt { method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane); - method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane); + method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState); } @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldRole {
diff --git a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt index 1305c09..c69428b 100644 --- a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt +++ b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
@@ -36,7 +36,7 @@ public final class ListDetailPaneScaffoldKt { method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane); - method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane); + method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState); } public final class ListDetailPaneScaffoldRole { @@ -60,6 +60,39 @@ property public final String Hidden; } + @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public abstract sealed class PaneExpansionAnchor { + } + + public static final class PaneExpansionAnchor.Offset extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor { + ctor public PaneExpansionAnchor.Offset(float offset); + method public float getOffset(); + property public final float offset; + } + + public static final class PaneExpansionAnchor.Proportion extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor { + ctor public PaneExpansionAnchor.Proportion(@FloatRange(from=0.0, to=1.0) float proportion); + method public float getProportion(); + property public final float proportion; + } + + public final class PaneExpansionDragHandleKt { + method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void PaneExpansionDragHandle(androidx.compose.material3.adaptive.layout.PaneExpansionState state, long color, optional androidx.compose.ui.Modifier modifier); + } + + @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public final class PaneExpansionState implements androidx.compose.foundation.gestures.DraggableState { + method public void clear(); + method public void dispatchRawDelta(float delta); + method public suspend Object? drag(androidx.compose.foundation.MutatePriority dragPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.DragScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>); + method public boolean isUnspecified(); + method public void setFirstPaneProportion(@FloatRange(from=0.0, to=1.0) float firstPaneProportion); + method public void setFirstPaneWidth(int firstPaneWidth); + field public static final androidx.compose.material3.adaptive.layout.PaneExpansionState.Companion Companion; + field public static final int Unspecified = -1; // 0xffffffff + } + + public static final class PaneExpansionState.Companion { + } + @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public sealed interface PaneExpansionStateKey { field public static final androidx.compose.material3.adaptive.layout.PaneExpansionStateKey.Companion Companion; } @@ -74,6 +107,11 @@ property public abstract androidx.compose.material3.adaptive.layout.PaneExpansionStateKey paneExpansionStateKey; } + public final class PaneExpansionStateKt { + method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.PaneExpansionState rememberPaneExpansionState(optional androidx.compose.material3.adaptive.layout.PaneExpansionStateKey key, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor> anchors); + method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.PaneExpansionState rememberPaneExpansionState(androidx.compose.material3.adaptive.layout.PaneExpansionStateKeyProvider keyProvider, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor> anchors); + } + public final class PaneKt { method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void AnimatedPane(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.AnimatedPaneScope,kotlin.Unit> content); } @@ -117,7 +155,7 @@ public final class SupportingPaneScaffoldKt { method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane); - method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane); + method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState); } @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldRole {
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScreenshotTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScreenshotTest.kt index d8bfa84..36723c0 100644 --- a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScreenshotTest.kt +++ b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScreenshotTest.kt
@@ -116,8 +116,9 @@ fun threePaneScaffold_paneExpansion_fixedFirstPaneWidth() { rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) { val mockPaneExpansionState = PaneExpansionState() - mockPaneExpansionState.firstPaneWidth = + mockPaneExpansionState.setFirstPaneWidth( with(LocalDensity.current) { 412.dp.roundToPx() } + ) SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState) } @@ -134,7 +135,7 @@ fun threePaneScaffold_paneExpansion_zeroFirstPaneWidth() { rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) { val mockPaneExpansionState = PaneExpansionState() - mockPaneExpansionState.firstPaneWidth = 0 + mockPaneExpansionState.setFirstPaneWidth(0) SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState) } @@ -151,8 +152,9 @@ fun threePaneScaffold_paneExpansion_overflowFirstPaneWidth() { rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) { val mockPaneExpansionState = PaneExpansionState() - mockPaneExpansionState.firstPaneWidth = + mockPaneExpansionState.setFirstPaneWidth( with(LocalDensity.current) { 1024.dp.roundToPx() } + ) SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState) } @@ -169,7 +171,7 @@ fun threePaneScaffold_paneExpansion_fixedFirstPanePercentage() { rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) { val mockPaneExpansionState = PaneExpansionState() - mockPaneExpansionState.firstPanePercentage = 0.5f + mockPaneExpansionState.setFirstPaneProportion(0.5f) SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState) } @@ -186,7 +188,7 @@ fun threePaneScaffold_paneExpansion_zeroFirstPanePercentage() { rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) { val mockPaneExpansionState = PaneExpansionState() - mockPaneExpansionState.firstPanePercentage = 0f + mockPaneExpansionState.setFirstPaneProportion(0f) SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState) } @@ -203,7 +205,7 @@ fun threePaneScaffold_paneExpansion_smallFirstPanePercentage() { rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) { val mockPaneExpansionState = PaneExpansionState() - mockPaneExpansionState.firstPanePercentage = 0.05f + mockPaneExpansionState.setFirstPaneProportion(0.05f) SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState) } @@ -220,7 +222,7 @@ fun threePaneScaffold_paneExpansion_largeFirstPanePercentage() { rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) { val mockPaneExpansionState = PaneExpansionState() - mockPaneExpansionState.firstPanePercentage = 0.95f + mockPaneExpansionState.setFirstPaneProportion(0.95f) SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState) } @@ -237,7 +239,7 @@ fun threePaneScaffold_paneExpansion_fullFirstPanePercentage() { rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) { val mockPaneExpansionState = PaneExpansionState() - mockPaneExpansionState.firstPanePercentage = 1.0f + mockPaneExpansionState.setFirstPaneProportion(1f) SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState) } @@ -254,8 +256,9 @@ fun threePaneScaffold_paneExpansionWithDragHandle_fixedFirstPaneWidth() { rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) { val mockPaneExpansionState = PaneExpansionState() - mockPaneExpansionState.firstPaneWidth = + mockPaneExpansionState.setFirstPaneWidth( with(LocalDensity.current) { 412.dp.roundToPx() } + ) SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState) { MockDragHandle(it) } } @@ -272,7 +275,7 @@ fun threePaneScaffold_paneExpansionWithDragHandle_zeroFirstPaneWidth() { rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) { val mockPaneExpansionState = PaneExpansionState() - mockPaneExpansionState.firstPaneWidth = 0 + mockPaneExpansionState.setFirstPaneWidth(0) SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState) { MockDragHandle(it) } } @@ -289,8 +292,9 @@ fun threePaneScaffold_paneExpansionWithDragHandle_overflowFirstPaneWidth() { rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) { val mockPaneExpansionState = PaneExpansionState() - mockPaneExpansionState.firstPaneWidth = + mockPaneExpansionState.setFirstPaneWidth( with(LocalDensity.current) { 1024.dp.roundToPx() } + ) SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState) { MockDragHandle(it) } }
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldTest.kt index 860b4dc..957275d 100644 --- a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldTest.kt +++ b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldTest.kt
@@ -270,9 +270,9 @@ @OptIn(ExperimentalMaterial3AdaptiveApi::class) private val MockPaneExpansionAnchors = listOf( - PaneExpansionAnchor(percentage = 0), - PaneExpansionAnchor(startOffset = MockPaneExpansionMiddleAnchor), - PaneExpansionAnchor(percentage = 100), + PaneExpansionAnchor.Proportion(0f), + PaneExpansionAnchor.Offset(MockPaneExpansionMiddleAnchor), + PaneExpansionAnchor.Proportion(1f), ) @OptIn(ExperimentalMaterial3AdaptiveApi::class)
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt index 2ad02fe..0403a04 100644 --- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt +++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt
@@ -47,6 +47,13 @@ * @param extraPane the extra pane of the scaffold, which is supposed to hold any supplementary info * besides the list and the detail panes, for example, a task list or a mini-calendar view of a * mail app. See [ListDetailPaneScaffoldRole.Extra]. + * @param paneExpansionDragHandle the pane expansion drag handle to let users be able to drag to + * change pane expansion state. Note that by default this argument will be `null`, and there won't + * be a drag handle rendered and users won't be able to drag to change the pane split. You can + * provide a [PaneExpansionDragHandle] here as our sample suggests. On the other hand, even if + * there's no drag handle, you can still modify [paneExpansionState] directly to apply pane + * expansion. + * @param paneExpansionState the state object of pane expansion. */ @ExperimentalMaterial3AdaptiveApi @Composable @@ -57,6 +64,8 @@ detailPane: @Composable ThreePaneScaffoldScope.() -> Unit, modifier: Modifier = Modifier, extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null, + paneExpansionDragHandle: (@Composable (PaneExpansionState) -> Unit)? = null, + paneExpansionState: PaneExpansionState = rememberPaneExpansionState(value), ) { ThreePaneScaffold( modifier = modifier.fillMaxSize(), @@ -65,6 +74,8 @@ paneOrder = ListDetailPaneScaffoldDefaults.PaneOrder, secondaryPane = listPane, tertiaryPane = extraPane, + paneExpansionDragHandle = paneExpansionDragHandle, + paneExpansionState = paneExpansionState, primaryPane = detailPane ) }
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.kt index 0a83188..8398174 100644 --- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.kt +++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.kt
@@ -30,14 +30,20 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.dp +/** + * A default, basic non-customizable implementation of pane expansion drag handle. Note that this + * implementation will be deprecated in favor of the corresponding Material3 implementation when + * it's available. + */ @ExperimentalMaterial3AdaptiveApi @Composable -// TODO(b/327637983): Implement this as a customizable component. -internal fun PaneExpansionDragHandle( +// TODO(b/327637983): Implement this as a customizable component as a Material3 component. +fun PaneExpansionDragHandle( state: PaneExpansionState, color: Color, modifier: Modifier = Modifier, ) { + // TODO (conradchen): support drag handle motion during scaffold and expansion state change Box( modifier = modifier.paneExpansionDragHandle(state).size(24.dp, 48.dp), contentAlignment = Alignment.Center
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionState.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionState.kt index 7a0d154..df95da6 100644 --- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionState.kt +++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionState.kt
@@ -16,18 +16,17 @@ package androidx.compose.material3.adaptive.layout -import androidx.annotation.IntRange +import androidx.annotation.FloatRange import androidx.annotation.VisibleForTesting import androidx.collection.IntList import androidx.collection.MutableIntList -import androidx.collection.emptyIntList import androidx.compose.animation.core.animate import androidx.compose.foundation.MutatePriority import androidx.compose.foundation.MutatorMutex import androidx.compose.foundation.gestures.DragScope import androidx.compose.foundation.gestures.DraggableState import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi -import androidx.compose.material3.adaptive.layout.PaneExpansionState.Companion.UnspecifiedWidth +import androidx.compose.material3.adaptive.layout.PaneExpansionState.Companion.Unspecified import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable @@ -40,8 +39,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.isSpecified import kotlin.math.abs +import kotlin.math.roundToInt import kotlinx.coroutines.coroutineScope /** @@ -96,7 +95,7 @@ */ @ExperimentalMaterial3AdaptiveApi @Composable -internal fun rememberPaneExpansionState( +fun rememberPaneExpansionState( keyProvider: PaneExpansionStateKeyProvider, anchors: List<PaneExpansionAnchor> = emptyList() ): PaneExpansionState = rememberPaneExpansionState(keyProvider.paneExpansionStateKey, anchors) @@ -112,7 +111,7 @@ */ @ExperimentalMaterial3AdaptiveApi @Composable -internal fun rememberPaneExpansionState( +fun rememberPaneExpansionState( key: PaneExpansionStateKey = PaneExpansionStateKey.Default, anchors: List<PaneExpansionAnchor> = emptyList() ): PaneExpansionState { @@ -129,32 +128,35 @@ } } +/** + * This class manages the pane expansion state for pane scaffolds. By providing and modifying an + * instance of this class, you can specify the expanded panes' expansion width or percentage when + * pane scaffold is displaying a dual-pane layout. + * + * This class also serves as the [DraggableState] of pane expansion handle. When a handle + * implementation is provided to the associated pane scaffold, the scaffold will use + * [PaneExpansionState] to store and manage dragging and anchoring of the handle, and thus the pane + * expansion state. + */ @ExperimentalMaterial3AdaptiveApi @Stable -internal class PaneExpansionState +class PaneExpansionState internal constructor( // TODO(conradchen): Handle state change during dragging and settling data: PaneExpansionStateData = PaneExpansionStateData(), - internal var anchors: List<PaneExpansionAnchor> = emptyList() + anchors: List<PaneExpansionAnchor> = emptyList() ) : DraggableState { - var firstPaneWidth: Int - set(value) { - data.firstPanePercentageState = Float.NaN - data.currentDraggingOffsetState = UnspecifiedWidth - val coercedValue = value.coerceIn(0, maxExpansionWidth) - data.firstPaneWidthState = coercedValue - } - get() = data.firstPaneWidthState + internal val firstPaneWidth + get() = + if (maxExpansionWidth == Unspecified || data.firstPaneWidthState == Unspecified) { + Unspecified + } else { + data.firstPaneWidthState.coerceIn(0, maxExpansionWidth) + } - var firstPanePercentage: Float - set(value) { - require(value in 0f..1f) { "Percentage value needs to be in [0, 1]" } - data.firstPaneWidthState = UnspecifiedWidth - data.currentDraggingOffsetState = UnspecifiedWidth - data.firstPanePercentageState = value - } - get() = data.firstPanePercentageState + internal val firstPaneProportion: Float + get() = data.firstPaneProportionState internal var currentDraggingOffset get() = data.currentDraggingOffsetState @@ -179,16 +181,18 @@ get() = isDragging || isSettling @VisibleForTesting - internal var maxExpansionWidth = 0 + internal var maxExpansionWidth by mutableIntStateOf(Unspecified) private set // Use this field to store the dragging offset decided by measuring instead of dragging to // prevent redundant re-composition. @VisibleForTesting - internal var currentMeasuredDraggingOffset = UnspecifiedWidth + internal var currentMeasuredDraggingOffset = Unspecified private set - private var anchorPositions: IntList = emptyIntList() + internal var anchors: List<PaneExpansionAnchor> by mutableStateOf(anchors) + + private lateinit var measuredDensity: Density private val dragScope: DragScope = object : DragScope { @@ -197,13 +201,14 @@ private val dragMutex = MutatorMutex() + /** Returns `true` if none of [firstPaneWidth] or [firstPaneProportion] has been set. */ fun isUnspecified(): Boolean = - firstPaneWidth == UnspecifiedWidth && - firstPanePercentage.isNaN() && - currentDraggingOffset == UnspecifiedWidth + firstPaneWidth == Unspecified && + firstPaneProportion.isNaN() && + currentDraggingOffset == Unspecified override fun dispatchRawDelta(delta: Float) { - if (currentMeasuredDraggingOffset == UnspecifiedWidth) { + if (currentMeasuredDraggingOffset == Unspecified) { return } currentDraggingOffset = (currentMeasuredDraggingOffset + delta).toInt() @@ -216,11 +221,45 @@ isDragging = false } - /** Clears any existing expansion state. */ + /** + * Set the width of the first expanded pane in the layout. When the set value gets applied, it + * will be coerced within the range of `[0, the full displayable width of the layout]`. + * + * Note that setting this value will reset the first pane proportion previously set via + * [setFirstPaneProportion] or the current dragging result if there's any. Also if user drags + * the pane after setting the first pane width, the user dragging result will take the priority + * over this set value when rendering panes, but the set value will be saved. + */ + fun setFirstPaneWidth(firstPaneWidth: Int) { + data.firstPaneProportionState = Float.NaN + data.currentDraggingOffsetState = Unspecified + data.firstPaneWidthState = firstPaneWidth + } + + /** + * Set the proportion of the first expanded pane in the layout. The set value needs to be within + * the range of `[0f, 1f]`, otherwise the setter throws. + * + * Note that setting this value will reset the first pane width previously set via + * [setFirstPaneWidth] or the current dragging result if there's any. Also if user drags the + * pane after setting the first pane proportion, the user dragging result will take the priority + * over this set value when rendering panes, but the set value will be saved. + */ + fun setFirstPaneProportion(@FloatRange(0.0, 1.0) firstPaneProportion: Float) { + require(firstPaneProportion in 0f..1f) { "Proportion value needs to be in [0f, 1f]" } + data.firstPaneWidthState = Unspecified + data.currentDraggingOffsetState = Unspecified + data.firstPaneProportionState = firstPaneProportion + } + + /** + * Clears any previously set [firstPaneWidth] or [firstPaneProportion], as well as the user + * dragging result. + */ fun clear() { - data.firstPaneWidthState = UnspecifiedWidth - data.firstPanePercentageState = Float.NaN - data.currentDraggingOffsetState = UnspecifiedWidth + data.firstPaneWidthState = Unspecified + data.firstPaneProportionState = Float.NaN + data.currentDraggingOffsetState = Unspecified } internal fun onMeasured(measuredWidth: Int, density: Density) { @@ -228,10 +267,11 @@ return } maxExpansionWidth = measuredWidth - if (firstPaneWidth != UnspecifiedWidth) { - firstPaneWidth = firstPaneWidth + measuredDensity = density + // To re-coerce the value + if (currentDraggingOffset != Unspecified) { + currentDraggingOffset = currentDraggingOffset } - anchorPositions = anchors.toPositions(measuredWidth, density) } internal fun onExpansionOffsetMeasured(measuredOffset: Int) { @@ -239,7 +279,7 @@ } internal suspend fun settleToAnchorIfNeeded(velocity: Float) { - val currentAnchorPositions = anchorPositions + val currentAnchorPositions = anchors.toPositions(maxExpansionWidth, measuredDensity) if (currentAnchorPositions.isEmpty()) { return } @@ -283,41 +323,72 @@ ) companion object { - const val UnspecifiedWidth = -1 + /** The constant value used to denote the pane expansion is not specified. */ + const val Unspecified = -1 + private const val AnchoringVelocityThreshold = 200F } } @OptIn(ExperimentalMaterial3AdaptiveApi::class) internal class PaneExpansionStateData { - var firstPaneWidthState by mutableIntStateOf(UnspecifiedWidth) - var firstPanePercentageState by mutableFloatStateOf(Float.NaN) - var currentDraggingOffsetState by mutableIntStateOf(UnspecifiedWidth) + var firstPaneWidthState by mutableIntStateOf(Unspecified) + var firstPaneProportionState by mutableFloatStateOf(Float.NaN) + var currentDraggingOffsetState by mutableIntStateOf(Unspecified) } +/** + * The implementations of this interface represent different types of anchors of pane expansion + * dragging. Setting up anchors when create [PaneExpansionState] will force user dragging to snap to + * the set anchors after user releases the drag. + */ @ExperimentalMaterial3AdaptiveApi -@Immutable -internal class PaneExpansionAnchor -private constructor( - val percentage: Int, - val startOffset: Dp // TODO(conradchen): confirm RTL support -) { - constructor(@IntRange(0, 100) percentage: Int) : this(percentage, Dp.Unspecified) +sealed class PaneExpansionAnchor private constructor() { + internal abstract fun positionIn(totalSizePx: Int, density: Density): Int - constructor(startOffset: Dp) : this(Int.MIN_VALUE, startOffset) + /** + * [PaneExpansionAnchor] implementation that specifies the anchor position in the proportion of + * the total size of the layout at the start side of the anchor. + * + * @property proportion the proportion of the layout at the start side of the anchor. layout. + */ + class Proportion(@FloatRange(0.0, 1.0) val proportion: Float) : PaneExpansionAnchor() { + override fun positionIn(totalSizePx: Int, density: Density) = + (totalSizePx * proportion).roundToInt().coerceIn(0, totalSizePx) - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is PaneExpansionAnchor) return false - if (percentage != other.percentage) return false - if (startOffset != other.startOffset) return false - return true + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Proportion) return false + return proportion == other.proportion + } + + override fun hashCode(): Int { + return proportion.hashCode() + } } - override fun hashCode(): Int { - var result = percentage - result = 31 * result + startOffset.hashCode() - return result + /** + * [PaneExpansionAnchor] implementation that specifies the anchor position in the offset in + * [Dp]. If a positive value is provided, the offset will be treated as a start offset, on the + * other hand, if a negative value is provided, the absolute value of the provided offset will + * be used as an end offset. For example, if -150.dp is provided, the resulted anchor will be at + * the position that is 150dp away from the end side of the associated layout. + * + * @property offset the offset of the anchor in [Dp]. + */ + class Offset(val offset: Dp) : PaneExpansionAnchor() { + override fun positionIn(totalSizePx: Int, density: Density) = + with(density) { offset.toPx() }.toInt().let { if (it < 0) totalSizePx + it else it } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Offset) return false + return offset == other.offset + } + + override fun hashCode(): Int { + return offset.hashCode() + } } } @@ -328,19 +399,7 @@ ): IntList { val anchors = MutableIntList(size) @Suppress("ListIterator") // Not necessarily a random-accessible list - forEach { anchor -> - if (anchor.startOffset.isSpecified) { - val position = - with(density) { anchor.startOffset.toPx() } - .toInt() - .let { if (it < 0) maxExpansionWidth + it else it } - if (position in 0..maxExpansionWidth) { - anchors.add(position) - } - } else { - anchors.add(maxExpansionWidth * anchor.percentage / 100) - } - } + forEach { anchor -> anchors.add(anchor.positionIn(maxExpansionWidth, density)) } anchors.sort() return anchors }
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt index 700e186..40d8c68 100644 --- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt +++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt
@@ -41,6 +41,13 @@ * @param extraPane the extra pane of the scaffold, which is supposed to hold any additional content * besides the main and the supporting panes, for example, a styling panel in a doc app. See * [SupportingPaneScaffoldRole.Extra]. + * @param paneExpansionDragHandle the pane expansion drag handle to let users be able to drag to + * change pane expansion state. Note that by default this argument will be `null`, and there won't + * be a drag handle rendered and users won't be able to drag to change the pane split. You can + * provide a [PaneExpansionDragHandle] here as our sample suggests. On the other hand, even if + * there's no drag handle, you can still modify [paneExpansionState] directly to apply pane + * expansion. + * @param paneExpansionState the state object of pane expansion. */ @ExperimentalMaterial3AdaptiveApi @Composable @@ -51,6 +58,8 @@ supportingPane: @Composable ThreePaneScaffoldScope.() -> Unit, modifier: Modifier = Modifier, extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null, + paneExpansionDragHandle: (@Composable (PaneExpansionState) -> Unit)? = null, + paneExpansionState: PaneExpansionState = rememberPaneExpansionState(value), ) { ThreePaneScaffold( modifier = modifier.fillMaxSize(), @@ -59,6 +68,8 @@ paneOrder = SupportingPaneScaffoldDefaults.PaneOrder, secondaryPane = supportingPane, tertiaryPane = extraPane, + paneExpansionDragHandle = paneExpansionDragHandle, + paneExpansionState = paneExpansionState, primaryPane = mainPane ) }
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt index b99b9f5..d85c569 100644 --- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt +++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
@@ -314,9 +314,7 @@ if (!paneExpansionState.isUnspecified() && visiblePanes.size == 2) { // Pane expansion should override everything - if ( - paneExpansionState.currentDraggingOffset != PaneExpansionState.UnspecifiedWidth - ) { + if (paneExpansionState.currentDraggingOffset != PaneExpansionState.Unspecified) { // Respect the user dragging result if there's any val halfSpacerSize = verticalSpacerSize / 2 if (paneExpansionState.currentDraggingOffset <= halfSpacerSize) { @@ -366,7 +364,7 @@ val availableWidth = constraints.maxWidth if ( paneExpansionState.firstPaneWidth == 0 || - paneExpansionState.firstPanePercentage == 0f + paneExpansionState.firstPaneProportion == 0f ) { measureAndPlacePaneWithLocalBounds( outerBounds, @@ -375,7 +373,7 @@ ) } else if ( paneExpansionState.firstPaneWidth >= availableWidth - verticalSpacerSize || - paneExpansionState.firstPanePercentage >= 1f + paneExpansionState.firstPaneProportion >= 1f ) { measureAndPlacePaneWithLocalBounds( outerBounds, @@ -385,12 +383,11 @@ } else { val firstPaneWidth = if ( - paneExpansionState.firstPaneWidth != - PaneExpansionState.UnspecifiedWidth + paneExpansionState.firstPaneWidth != PaneExpansionState.Unspecified ) { paneExpansionState.firstPaneWidth } else { - (paneExpansionState.firstPanePercentage * + (paneExpansionState.firstPaneProportion * (availableWidth - verticalSpacerSize)) .toInt() } @@ -505,7 +502,7 @@ if ( !paneExpansionState.isDraggingOrSettling || paneExpansionState.currentDraggingOffset == - PaneExpansionState.UnspecifiedWidth + PaneExpansionState.Unspecified ) { val spacerMiddleOffset = getSpacerMiddleOffsetX(visiblePanes[0], visiblePanes[1]) @@ -524,7 +521,7 @@ handleOffsetX ) } else if (!isLookingAhead) { - paneExpansionState.onExpansionOffsetMeasured(PaneExpansionState.UnspecifiedWidth) + paneExpansionState.onExpansionOffsetMeasured(PaneExpansionState.Unspecified) } // Place the hidden panes to ensure a proper motion at the AnimatedVisibility, @@ -703,7 +700,7 @@ maxHandleWidth: Int, offsetX: Int ) { - if (offsetX == PaneExpansionState.UnspecifiedWidth) { + if (offsetX == PaneExpansionState.Unspecified) { return } val placeables = @@ -724,7 +721,7 @@ (paneLeft.placedPositionX + paneLeft.measuredWidth + paneRight.placedPositionX) / 2 paneLeft.measuredAndPlaced -> paneLeft.placedPositionX + paneLeft.measuredWidth paneRight.measuredAndPlaced -> 0 - else -> PaneExpansionState.UnspecifiedWidth + else -> PaneExpansionState.Unspecified } } }
diff --git a/compose/material3/adaptive/adaptive-navigation/api/current.txt b/compose/material3/adaptive/adaptive-navigation/api/current.txt index c1f74e3..962ebd8 100644 --- a/compose/material3/adaptive/adaptive-navigation/api/current.txt +++ b/compose/material3/adaptive/adaptive-navigation/api/current.txt
@@ -2,8 +2,8 @@ package androidx.compose.material3.adaptive.navigation { public final class AndroidThreePaneScaffold_androidKt { - method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableListDetailPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior); - method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableSupportingPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior); + method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableListDetailPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState); + method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableSupportingPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState); } @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @kotlin.jvm.JvmInline public final value class BackNavigationBehavior {
diff --git a/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt b/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt index c1f74e3..962ebd8 100644 --- a/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt +++ b/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt
@@ -2,8 +2,8 @@ package androidx.compose.material3.adaptive.navigation { public final class AndroidThreePaneScaffold_androidKt { - method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableListDetailPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior); - method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableSupportingPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior); + method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableListDetailPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState); + method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableSupportingPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState); } @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @kotlin.jvm.JvmInline public final value class BackNavigationBehavior {
diff --git a/compose/material3/adaptive/adaptive-navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/AndroidThreePaneScaffold.android.kt b/compose/material3/adaptive/adaptive-navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/AndroidThreePaneScaffold.android.kt index 1efa9f4..e55f244 100644 --- a/compose/material3/adaptive/adaptive-navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/AndroidThreePaneScaffold.android.kt +++ b/compose/material3/adaptive/adaptive-navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/AndroidThreePaneScaffold.android.kt
@@ -19,8 +19,11 @@ import androidx.activity.compose.BackHandler import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold as BaseListDetailPaneScaffold +import androidx.compose.material3.adaptive.layout.PaneExpansionDragHandle +import androidx.compose.material3.adaptive.layout.PaneExpansionState import androidx.compose.material3.adaptive.layout.SupportingPaneScaffold as BaseSupportingPaneScaffold import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope +import androidx.compose.material3.adaptive.layout.rememberPaneExpansionState import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -41,6 +44,13 @@ * mail app. See [ListDetailPaneScaffoldRole.Extra]. * @param defaultBackBehavior the default back navigation behavior when the system back event * happens. See [BackNavigationBehavior] for the use cases of each behavior. + * @param paneExpansionDragHandle the pane expansion drag handle to let users be able to drag to + * change pane expansion state. Note that by default this argument will be `null`, and there won't + * be a drag handle rendered and users won't be able to drag to change the pane split. You can + * provide a [PaneExpansionDragHandle] here as our sample suggests. On the other hand, even if + * there's no drag handle, you can still modify [paneExpansionState] directly to apply pane + * expansion. + * @param paneExpansionState the state object of pane expansion. */ @ExperimentalMaterial3AdaptiveApi @Composable @@ -50,7 +60,9 @@ detailPane: @Composable ThreePaneScaffoldScope.() -> Unit, modifier: Modifier = Modifier, extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null, - defaultBackBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilContentChange + defaultBackBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilContentChange, + paneExpansionDragHandle: (@Composable (PaneExpansionState) -> Unit)? = null, + paneExpansionState: PaneExpansionState = rememberPaneExpansionState(navigator.scaffoldValue), ) { // TODO(b/330584029): support predictive back BackHandler(enabled = navigator.canNavigateBack(defaultBackBehavior)) { @@ -62,7 +74,9 @@ value = navigator.scaffoldValue, detailPane = detailPane, listPane = listPane, - extraPane = extraPane + extraPane = extraPane, + paneExpansionDragHandle = paneExpansionDragHandle, + paneExpansionState = paneExpansionState, ) } @@ -82,6 +96,13 @@ * [SupportingPaneScaffoldRole.Extra]. * @param defaultBackBehavior the default back navigation behavior when the system back event * happens. See [BackNavigationBehavior] for the use cases of each behavior. + * @param paneExpansionDragHandle the pane expansion drag handle to let users be able to drag to + * change pane expansion state. Note that by default this argument will be `null`, and there won't + * be a drag handle rendered and users won't be able to drag to change the pane split. You can + * provide a [PaneExpansionDragHandle] here as our sample suggests. On the other hand, even if + * there's no drag handle, you can still modify [paneExpansionState] directly to apply pane + * expansion. + * @param paneExpansionState the state object of pane expansion. */ @ExperimentalMaterial3AdaptiveApi @Composable @@ -91,7 +112,9 @@ supportingPane: @Composable ThreePaneScaffoldScope.() -> Unit, modifier: Modifier = Modifier, extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null, - defaultBackBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilContentChange + defaultBackBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilContentChange, + paneExpansionDragHandle: (@Composable (PaneExpansionState) -> Unit)? = null, + paneExpansionState: PaneExpansionState = rememberPaneExpansionState(navigator.scaffoldValue), ) { // TODO(b/330584029): support predictive back BackHandler(enabled = navigator.canNavigateBack(defaultBackBehavior)) { @@ -103,6 +126,8 @@ value = navigator.scaffoldValue, mainPane = mainPane, supportingPane = supportingPane, - extraPane = extraPane + extraPane = extraPane, + paneExpansionDragHandle = paneExpansionDragHandle, + paneExpansionState = paneExpansionState, ) }
diff --git a/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt b/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt index 3a44336..77e13de 100644 --- a/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt +++ b/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt
@@ -54,6 +54,9 @@ import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole import androidx.compose.material3.adaptive.layout.PaneAdaptedValue +import androidx.compose.material3.adaptive.layout.PaneExpansionAnchor +import androidx.compose.material3.adaptive.layout.PaneExpansionDragHandle +import androidx.compose.material3.adaptive.layout.rememberPaneExpansionState import androidx.compose.material3.adaptive.navigation.BackNavigationBehavior import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator @@ -174,6 +177,14 @@ Text("Extra") } } + }, + paneExpansionState = + rememberPaneExpansionState( + keyProvider = scaffoldNavigator.scaffoldValue, + anchors = PaneExpansionAnchors + ), + paneExpansionDragHandle = { state -> + PaneExpansionDragHandle(state = state, color = MaterialTheme.colorScheme.outline) } ) } @@ -362,3 +373,11 @@ } } } + +@OptIn(ExperimentalMaterial3AdaptiveApi::class) +private val PaneExpansionAnchors = + listOf( + PaneExpansionAnchor.Proportion(0f), + PaneExpansionAnchor.Proportion(0.5f), + PaneExpansionAnchor.Proportion(1f), + )