diff --git a/build-logic/gradm/gradm.yml b/build-logic/gradm/gradm.yml index 983601a..a53769e 100644 --- a/build-logic/gradm/gradm.yml +++ b/build-logic/gradm/gradm.yml @@ -1,12 +1,15 @@ versions: circuit: "0.23.1" - compose: - annotation: "1.7.0-beta01" - collection: "1.7.0-beta01" - multiplatform: "1.7.0-beta01" consensus: "0.15.0" delusion: "0.9.0" gradle: "8.9" + jetbrains: + androidx: + annotation: "1.7.0-beta01" + collection: "1.7.0-beta01" + compose.material3.adaptive: "1.0.0-alpha02" + window: "1.3.0-alpha02" + compose: "1.7.0-beta01" kotlin: "2.0.20" kotlinx: coroutines: "1.9.0-RC.2" @@ -28,7 +31,7 @@ plugins: gradlePluginPortal: com.diffplug.spotless: ${versions.plugins.spotless} com.gradle.develocity: ${versions.plugins.develocity} - org.jetbrains.compose: ${versions.compose.multiplatform} + org.jetbrains.compose: ${versions.jetbrains.compose} org.jetbrains.kotlin.plugin.compose: ${versions.kotlin} org.jetbrains.kotlin.plugin.serialization: ${versions.kotlin} @@ -38,14 +41,28 @@ dependencies: circuit-foundation: alias: circuit.foundation version: ${versions.circuit} + org.jetbrains.androidx.window: + window-core: + alias: jetbrains.androidx.window.core + version: ${versions.jetbrains.androidx.window}} org.jetbrains.compose.annotation-internal: annotation: alias: jetbrains.compose.annotation - version: ${versions.compose.annotation} + version: ${versions.jetbrains.androidx.annotation}} org.jetbrains.compose.collection-internal: collection: alias: jetbrains.compose.collection - version: ${versions.compose.collection} + version: ${versions.jetbrains.androidx.collection}} + org.jetbrains.compose.material3.adaptive: + adaptive: + alias: jetbrains.compose.material3.adaptive + version: ${versions.jetbrains.androidx.compose.material3.adaptive} + adaptive-layout: + alias: jetbrains.compose.material3.adaptive.layout + version: ${versions.jetbrains.androidx.compose.material3.adaptive} + adaptive-navigation: + alias: jetbrains.compose.material3.adaptive.navigation + version: ${versions.jetbrains.androidx.compose.material3.adaptive} org.jetbrains.kotlin: kotlin-gradle-plugin: alias: kotlinGradlePlugin diff --git a/build-logic/project/src/main/kotlin/wwm.compose.gradle.kts b/build-logic/project/src/main/kotlin/wwm.compose.gradle.kts index 705c467..a2af0c5 100644 --- a/build-logic/project/src/main/kotlin/wwm.compose.gradle.kts +++ b/build-logic/project/src/main/kotlin/wwm.compose.gradle.kts @@ -12,5 +12,5 @@ composeCompiler { } configureCommonComposeMultiplatform( - composeMultiplatformVersion = Versions.compose.multiplatform, + composeMultiplatformVersion = Versions.jetbrains.compose, ) diff --git a/build-logic/project/src/main/kotlin/wwm.root.androidx.gradle.kts b/build-logic/project/src/main/kotlin/wwm.root.androidx.gradle.kts index 72af86e..fec6f47 100644 --- a/build-logic/project/src/main/kotlin/wwm.root.androidx.gradle.kts +++ b/build-logic/project/src/main/kotlin/wwm.root.androidx.gradle.kts @@ -9,8 +9,8 @@ val fetchAndroidx: FetchAndroidx by tasks.register("fetchAndroidx val androidxDirectory = rootProject.consensus.localProperties.getOrNull("ANDROIDX_DIRECTORY") enabled = androidxDirectory != null androidxDirectoryProperty.set(androidxDirectory) - // https://developer.android.com/jetpack/androidx/releases/compose-material3-adaptive#1.0.0-beta04 - commitIdProperty.set("967bd940b24778daf0ac5f9661bc881f4d0487c1") + // https://developer.android.com/jetpack/androidx/releases/compose-material3-adaptive#1.0.0 + commitIdProperty.set("cad2089d1b7edd842b0132ba03a6d2fa4ee7d1a1") } val syncAndroidx: SyncAndroidx by tasks.register("syncAndroidx") { @@ -18,18 +18,6 @@ val syncAndroidx: SyncAndroidx by tasks.register("syncAndroidx") { androidxDirectoryProperty.set(fetchAndroidx.androidxDirectoryProperty) copySpecsProperty.set( listOf( - SyncAndroidx.CopySpec( - sourcePath = "compose/material3/adaptive/adaptive", - targetPath = "wwm/androidx/compose/material3/adaptive", - ), - SyncAndroidx.CopySpec( - sourcePath = "compose/material3/adaptive/adaptive-layout", - targetPath = "wwm/androidx/compose/material3/adaptive/layout", - ), - SyncAndroidx.CopySpec( - sourcePath = "compose/material3/adaptive/adaptive-navigation", - targetPath = "wwm/androidx/compose/material3/adaptive/navigation", - ), SyncAndroidx.CopySpec( sourcePath = "compose/material3/material3-adaptive-navigation-suite", targetPath = "wwm/androidx/compose/material3/adaptive/navigation/suite", diff --git a/wwm/androidx/compose/material3/adaptive/.gitignore b/wwm/androidx/compose/material3/adaptive/.gitignore deleted file mode 100644 index 796b96d..0000000 --- a/wwm/androidx/compose/material3/adaptive/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/wwm/androidx/compose/material3/adaptive/build.gradle.kts b/wwm/androidx/compose/material3/adaptive/build.gradle.kts deleted file mode 100644 index 71c1adf..0000000 --- a/wwm/androidx/compose/material3/adaptive/build.gradle.kts +++ /dev/null @@ -1,16 +0,0 @@ -plugins { - id("wwm.compose.multiplatform") -} - -kotlin { - sourceSets { - commonMain { - dependencies { - implementation(project(":wwm-androidx-window-core")) - } - dependencies { - implementation(compose.ui) - } - } - } -} diff --git a/wwm/androidx/compose/material3/adaptive/layout/.gitignore b/wwm/androidx/compose/material3/adaptive/layout/.gitignore deleted file mode 100644 index 796b96d..0000000 --- a/wwm/androidx/compose/material3/adaptive/layout/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/wwm/androidx/compose/material3/adaptive/layout/build.gradle.kts b/wwm/androidx/compose/material3/adaptive/layout/build.gradle.kts deleted file mode 100644 index ebeac43..0000000 --- a/wwm/androidx/compose/material3/adaptive/layout/build.gradle.kts +++ /dev/null @@ -1,25 +0,0 @@ -plugins { - id("wwm.compose.multiplatform") -} - -kotlin { - sourceSets { - all { - languageSettings { - enableLanguageFeature("ContextReceivers") - } - } - commonMain { - dependencies { - implementation(project(":wwm-androidx-compose-material3-adaptive")) - implementation(project(":wwm-androidx-window-core")) - } - dependencies { - implementation(compose.animation) - implementation(compose.foundation) - implementation(jetbrains.compose.annotation) - implementation(jetbrains.compose.collection) - } - } - } -} diff --git a/wwm/androidx/compose/material3/adaptive/layout/src/androidMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.android.kt b/wwm/androidx/compose/material3/adaptive/layout/src/androidMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.android.kt deleted file mode 100644 index 6377185..0000000 --- a/wwm/androidx/compose/material3/adaptive/layout/src/androidMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.android.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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.compose.material3.adaptive.layout - -import androidx.compose.foundation.systemGestureExclusion as androidSystemGestureExclusion -import androidx.compose.ui.Modifier - -internal actual fun Modifier.systemGestureExclusion(): Modifier = - this.androidSystemGestureExclusion() diff --git a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AdaptStrategy.kt b/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AdaptStrategy.kt deleted file mode 100644 index 3fe6961..0000000 --- a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AdaptStrategy.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2023 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.compose.material3.adaptive.layout - -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.Stable - -/** - * Provides the information about how the associated pane should be adapted if it cannot be - * displayed in its [PaneAdaptedValue.Expanded] state. - */ -@ExperimentalMaterial3AdaptiveApi -@Stable -sealed interface AdaptStrategy { - /** Override this function to provide the resulted adapted state. */ - fun adapt(): PaneAdaptedValue - - @Immutable - private class SimpleAdaptStrategy( - private val description: String, - private val adaptedValue: PaneAdaptedValue, - ) : AdaptStrategy { - override fun adapt() = adaptedValue - - override fun toString() = "AdaptStrategy[$description]" - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is SimpleAdaptStrategy) return false - if (description != other.description) return false - if (adaptedValue != other.adaptedValue) return false - return true - } - - override fun hashCode(): Int { - var result = description.hashCode() - result = 31 * result + adaptedValue.hashCode() - return result - } - } - - companion object { - /** - * The default [AdaptStrategy] that suggests the layout to hide the associated pane when it - * has to be adapted, i.e., cannot be displayed in its [PaneAdaptedValue.Expanded] state. - */ - val Hide: AdaptStrategy = SimpleAdaptStrategy("Hide", PaneAdaptedValue.Hidden) - } -} diff --git a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AnimateBoundsModifier.kt b/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AnimateBoundsModifier.kt deleted file mode 100644 index dd6774b..0000000 --- a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/AnimateBoundsModifier.kt +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright 2023 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.compose.material3.adaptive.layout - -import androidx.compose.animation.core.AnimationVector2D -import androidx.compose.animation.core.FiniteAnimationSpec -import androidx.compose.animation.core.TargetBasedAnimation -import androidx.compose.animation.core.VectorConverter -import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.layout.ApproachLayoutModifierNode -import androidx.compose.ui.layout.ApproachMeasureScope -import androidx.compose.ui.layout.LayoutCoordinates -import androidx.compose.ui.layout.LookaheadScope -import androidx.compose.ui.layout.Measurable -import androidx.compose.ui.layout.MeasureResult -import androidx.compose.ui.layout.Placeable -import androidx.compose.ui.node.ModifierNodeElement -import androidx.compose.ui.platform.InspectorInfo -import androidx.compose.ui.platform.debugInspectorInfo -import androidx.compose.ui.unit.Constraints -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.IntSize -import kotlin.math.roundToInt - -internal fun Modifier.animateBounds( - animateFraction: () -> Float, - sizeAnimationSpec: FiniteAnimationSpec, - positionAnimationSpec: FiniteAnimationSpec, - lookaheadScope: LookaheadScope, - enabled: Boolean, -) = - if (enabled) { - this.then( - AnimateBoundsElement( - animateFraction, - sizeAnimationSpec, - positionAnimationSpec, - lookaheadScope - ) - ) - } else { - this - } - -private data class AnimateBoundsElement( - private val animateFraction: () -> Float, - private val sizeAnimationSpec: FiniteAnimationSpec, - private val positionAnimationSpec: FiniteAnimationSpec, - private val lookaheadScope: LookaheadScope, -) : ModifierNodeElement() { - private val inspectorInfo = debugInspectorInfo { - name = "animateBounds" - properties["animateFraction"] = animateFraction - properties["sizeAnimationSpec"] = sizeAnimationSpec - properties["positionAnimationSpec"] = positionAnimationSpec - properties["lookaheadScope"] = lookaheadScope - } - - override fun create(): AnimateBoundsNode { - return AnimateBoundsNode( - animateFraction, - sizeAnimationSpec, - positionAnimationSpec, - lookaheadScope, - ) - } - - override fun update(node: AnimateBoundsNode) { - node.animateFraction = animateFraction - node.sizeAnimationSpec = sizeAnimationSpec - node.positionAnimationSpec = positionAnimationSpec - node.lookaheadScope = lookaheadScope - } - - override fun InspectorInfo.inspectableProperties() { - inspectorInfo() - } -} - -private class AnimateBoundsNode( - var animateFraction: () -> Float, - sizeAnimationSpec: FiniteAnimationSpec, - positionAnimationSpec: FiniteAnimationSpec, - var lookaheadScope: LookaheadScope, -) : ApproachLayoutModifierNode, Modifier.Node() { - val sizeTracker = SizeTracker(sizeAnimationSpec) - val positionTracker = PositionTracker(positionAnimationSpec) - - var sizeAnimationSpec - set(value) { - sizeTracker.animationSpec = value - } - get() = sizeTracker.animationSpec - - var positionAnimationSpec - set(value) { - positionTracker.animationSpec = value - } - get() = positionTracker.animationSpec - - override fun isMeasurementApproachInProgress(lookaheadSize: IntSize): Boolean = - animateFraction() != 1f - - override fun Placeable.PlacementScope.isPlacementApproachInProgress( - lookaheadCoordinates: LayoutCoordinates, - ) = animateFraction() != 1f - - override fun ApproachMeasureScope.approachMeasure( - measurable: Measurable, - constraints: Constraints, - ): MeasureResult { - // When layout changes, the lookahead pass will calculate a new final size for the - // child modifier. This lookahead size can be used to animate the size - // change, such that the animation starts from the current size and gradually - // change towards `lookaheadSize`. - sizeTracker.updateTargetSize(lookaheadSize) - val (width, height) = sizeTracker.updateAndGetCurrentSize(animateFraction()) - // Creates a fixed set of constraints using the animated size - val animatedConstraints = Constraints.fixed(width, height) - // Measure child/children with animated constraints. - val placeable = measurable.measure(animatedConstraints) - return layout(placeable.width, placeable.height) { - coordinates?.let { - positionTracker.updateTargetOffset( - with(lookaheadScope) { - lookaheadScopeCoordinates.localLookaheadPositionOf(it).toIntOffset() - } - ) - placeable.place( - with(lookaheadScope) { - positionTracker.updateAndGetCurrentOffset(animateFraction()) - - lookaheadScopeCoordinates.localPositionOf(it, Offset.Zero).toIntOffset() - } - ) - } - } - } -} - -private class SizeTracker(var animationSpec: FiniteAnimationSpec) { - private var originalSize: IntSize = InvalidIntSize - private var targetSize: IntSize = InvalidIntSize - private var currentSize = InvalidIntSize - private lateinit var animation: TargetBasedAnimation - - fun updateTargetSize(newSize: IntSize) { - if (targetSize == newSize) { - return - } - // TODO(conradchen): Handle the interruption better when the target size changes during - // the animation - originalSize = - if (currentSize != InvalidIntSize) { - currentSize - } else { - newSize - } - targetSize = newSize - animation = - TargetBasedAnimation(animationSpec, IntSize.VectorConverter, originalSize, targetSize) - } - - fun updateAndGetCurrentSize(fraction: Float): IntSize { - currentSize = animation.getValueFromNanos((animation.durationNanos * fraction).toLong()) - return currentSize - } -} - -private class PositionTracker(var animationSpec: FiniteAnimationSpec) { - private var originalOffset: IntOffset? = null - private var targetOffset: IntOffset? = null - private var currentOffset: IntOffset? = null - private lateinit var animation: TargetBasedAnimation - - fun updateTargetOffset(newOffset: IntOffset) { - if (targetOffset == newOffset) { - return - } - // TODO(conradchen): Handle the interruption better when the target position changes during - // the animation - originalOffset = - if (currentOffset != null) { - currentOffset - } else { - newOffset - } - targetOffset = newOffset - animation = - TargetBasedAnimation( - animationSpec, - IntOffset.VectorConverter, - originalOffset!!, - targetOffset!! - ) - } - - fun updateAndGetCurrentOffset(fraction: Float): IntOffset { - currentOffset = animation.getValueFromNanos((animation.durationNanos * fraction).toLong()) - return currentOffset!! - } -} - -private fun Offset.toIntOffset() = IntOffset(x.roundToInt(), y.roundToInt()) - -private val InvalidIntSize = IntSize(-1, -1) diff --git a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt b/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt deleted file mode 100644 index 0294dba..0000000 --- a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2023 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.compose.material3.adaptive.layout - -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier - -/** - * A Material opinionated implementation of [ThreePaneScaffold] that will display the provided three - * panes in a canonical list-detail layout. - * - * See usage samples at: - * - * @sample androidx.compose.material3.adaptive.samples.ListDetailPaneScaffoldSample - * @sample androidx.compose.material3.adaptive.samples.ListDetailPaneScaffoldSampleWithExtraPane - * @sample androidx.compose.material3.adaptive.samples.ListDetailPaneScaffoldWithNavigationSample - * @param directive The top-level directives about how the scaffold should arrange its panes. - * @param value The current adapted value of the scaffold, which indicates how each pane of the - * scaffold is adapted. - * @param listPane the list pane of the scaffold, which is supposed to hold a list of item summaries - * that can be selected from, for example, the inbox mail list of a mail app. See - * [ListDetailPaneScaffoldRole.List]. - * @param detailPane the detail pane of the scaffold, which is supposed to hold the detailed info of - * a selected item, for example, the mail content currently being viewed. See - * [ListDetailPaneScaffoldRole.Detail]. - * @param modifier [Modifier] of the scaffold layout. - * @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]. - */ -@ExperimentalMaterial3AdaptiveApi -@Composable -fun ListDetailPaneScaffold( - directive: PaneScaffoldDirective, - value: ThreePaneScaffoldValue, - listPane: @Composable ThreePaneScaffoldScope.() -> Unit, - detailPane: @Composable ThreePaneScaffoldScope.() -> Unit, - modifier: Modifier = Modifier, - extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null, -) { - ThreePaneScaffold( - modifier = modifier.fillMaxSize(), - scaffoldDirective = directive, - scaffoldValue = value, - paneOrder = ListDetailPaneScaffoldDefaults.PaneOrder, - secondaryPane = listPane, - tertiaryPane = extraPane, - primaryPane = detailPane - ) -} - -/** Provides default values of [ListDetailPaneScaffold]. */ -@ExperimentalMaterial3AdaptiveApi -object ListDetailPaneScaffoldDefaults { - /** - * Creates a default [ThreePaneScaffoldAdaptStrategies] for [ListDetailPaneScaffold]. - * - * @param detailPaneAdaptStrategy the adapt strategy of the primary pane - * @param listPaneAdaptStrategy the adapt strategy of the secondary pane - * @param extraPaneAdaptStrategy the adapt strategy of the tertiary pane - */ - fun adaptStrategies( - detailPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide, - listPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide, - extraPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide, - ): ThreePaneScaffoldAdaptStrategies = - ThreePaneScaffoldAdaptStrategies( - detailPaneAdaptStrategy, - listPaneAdaptStrategy, - extraPaneAdaptStrategy - ) - - /** - * Denotes [ThreePaneScaffold] to use the list-detail pane-order to arrange its panes - * horizontally, which allocates panes in the order of secondary, primary, and tertiary from - * start to end. - */ - internal val PaneOrder = - ThreePaneScaffoldHorizontalOrder( - ThreePaneScaffoldRole.Secondary, - ThreePaneScaffoldRole.Primary, - ThreePaneScaffoldRole.Tertiary - ) -} - -/** - * The set of the available pane roles of [ListDetailPaneScaffold]. Those roles map to their - * corresponding [ThreePaneScaffoldRole], which is a generic role definition across all types of - * three pane scaffolds. We suggest you to use the values defined here instead of the raw - * [ThreePaneScaffoldRole] under the context of [ListDetailPaneScaffold] for better code clarity. - */ -object ListDetailPaneScaffoldRole { - /** - * The list pane of [ListDetailPaneScaffold], which is supposed to hold a list of item summaries - * that can be selected from, for example, the inbox mail list of a mail app. It maps to - * [ThreePaneScaffoldRole.Secondary]. - */ - val List = ThreePaneScaffoldRole.Secondary - - /** - * The detail pane of [ListDetailPaneScaffold], which is supposed to hold the detailed info of a - * selected item, for example, the mail content currently being viewed. It maps to - * [ThreePaneScaffoldRole.Primary]. - */ - val Detail = ThreePaneScaffoldRole.Primary - - /** - * The extra pane of [ListDetailPaneScaffold], 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. It maps to [ThreePaneScaffoldRole.Tertiary]. - */ - val Extra = ThreePaneScaffoldRole.Tertiary -} diff --git a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/Pane.kt b/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/Pane.kt deleted file mode 100644 index 2fc682c..0000000 --- a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/Pane.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.compose.material3.adaptive.layout - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.AnimatedVisibilityScope -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clipToBounds - -/** - * The root composable of pane contents in a [ThreePaneScaffold] that supports default motions - * during pane switching. It's recommended to use this composable to wrap your own contents when - * passing them into pane parameters of the scaffold functions, therefore your panes can have a nice - * default animation for free. - * - * @param modifier The modifier applied to the [AnimatedPane]. - * @param content The content of the [AnimatedPane]. Also see [AnimatedPaneScope]. - * - * See usage samples at: - * - * @sample androidx.compose.material3.adaptive.samples.ListDetailPaneScaffoldSample - * @sample androidx.compose.material3.adaptive.samples.ListDetailPaneScaffoldSampleWithExtraPane - */ -@ExperimentalMaterial3AdaptiveApi -@Composable -fun ThreePaneScaffoldScope.AnimatedPane( - modifier: Modifier = Modifier, - content: (@Composable AnimatedPaneScope.() -> Unit), -) { - val keepShowing = - scaffoldStateTransition.currentState[role] != PaneAdaptedValue.Hidden && - scaffoldStateTransition.targetState[role] != PaneAdaptedValue.Hidden - val animateFraction = { scaffoldStateTransitionFraction } - scaffoldStateTransition.AnimatedVisibility( - visible = { value: ThreePaneScaffoldValue -> value[role] != PaneAdaptedValue.Hidden }, - modifier = - modifier - .animatedPane() - .animateBounds( - animateFraction = animateFraction, - positionAnimationSpec = positionAnimationSpec, - sizeAnimationSpec = sizeAnimationSpec, - lookaheadScope = this, - enabled = keepShowing - ) - .then(if (keepShowing) Modifier else Modifier.clipToBounds()), - enter = enterTransition, - exit = exitTransition - ) { - AnimatedPaneScopeImpl(this).content() - } -} - -/** - * Scope for the content of [AnimatedPane]. It extends from the necessary animation scopes so - * developers can use the info carried by the scopes to do certain customizations. - */ -sealed interface AnimatedPaneScope : AnimatedVisibilityScope - -private class AnimatedPaneScopeImpl(animatedVisibilityScope: AnimatedVisibilityScope) : - AnimatedPaneScope, AnimatedVisibilityScope by animatedVisibilityScope diff --git a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneAdaptedValue.kt b/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneAdaptedValue.kt deleted file mode 100644 index 73cbd4a..0000000 --- a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneAdaptedValue.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2023 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.compose.material3.adaptive.layout - -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi -import kotlin.jvm.JvmInline - -/** - * The adapted state of a pane. It gives clues to pane scaffolds about if a certain pane should be - * composed and how. - */ -@ExperimentalMaterial3AdaptiveApi -@JvmInline -value class PaneAdaptedValue private constructor(private val description: String) { - companion object { - /** Denotes that the associated pane should be displayed in its full width and height. */ - val Expanded = PaneAdaptedValue("Expanded") - - /** Denotes that the associated pane should be hidden. */ - val Hidden = PaneAdaptedValue("Hidden") - } -} diff --git a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.kt b/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.kt deleted file mode 100644 index a60d376..0000000 --- a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.compose.material3.adaptive.layout - -import androidx.compose.foundation.background -import androidx.compose.foundation.gestures.Orientation -import androidx.compose.foundation.gestures.draggable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.unit.dp - -@ExperimentalMaterial3AdaptiveApi -@Composable -// TODO(b/327637983): Implement this as a customizable component. -internal fun PaneExpansionDragHandle( - state: PaneExpansionState, - color: Color, - modifier: Modifier = Modifier, -) { - Box( - modifier = modifier.paneExpansionDragHandle(state).size(24.dp, 48.dp), - contentAlignment = Alignment.Center - ) { - Box( - modifier = - Modifier.size(4.dp, 48.dp) - .graphicsLayer(shape = CircleShape, clip = true) - .background(color) - ) - } -} - -@ExperimentalMaterial3AdaptiveApi -internal fun Modifier.paneExpansionDragHandle(state: PaneExpansionState): Modifier = - this.draggable( - state = state, - orientation = Orientation.Horizontal, - onDragStopped = { velocity -> state.settleToAnchorIfNeeded(velocity) } - ) - .systemGestureExclusion() - -internal expect fun Modifier.systemGestureExclusion(): Modifier diff --git a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionState.kt b/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionState.kt deleted file mode 100644 index 08c4cb6..0000000 --- a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionState.kt +++ /dev/null @@ -1,242 +0,0 @@ -/* - * 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.compose.material3.adaptive.layout - -import androidx.annotation.IntRange -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.runtime.Immutable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.isSpecified -import kotlinx.coroutines.coroutineScope -import kotlin.math.abs - -@ExperimentalMaterial3AdaptiveApi -@Stable -internal class PaneExpansionState(internal val anchors: List = emptyList()) : - DraggableState { - private var firstPaneWidthState by mutableIntStateOf(UnspecifiedWidth) - private var firstPanePercentageState by mutableFloatStateOf(Float.NaN) - private var currentDraggingOffsetState by mutableIntStateOf(UnspecifiedWidth) - - var firstPaneWidth: Int - set(value) { - firstPanePercentageState = Float.NaN - currentDraggingOffsetState = UnspecifiedWidth - firstPaneWidthState = value.coerceIn(0, maxExpansionWidth) - } - get() = firstPaneWidthState - - var firstPanePercentage: Float - set(value) { - require(value in 0f..1f) { "Percentage value needs to be in [0, 1]" } - firstPaneWidthState = UnspecifiedWidth - currentDraggingOffsetState = UnspecifiedWidth - firstPanePercentageState = value - } - get() = firstPanePercentageState - - internal var currentDraggingOffset - get() = currentDraggingOffsetState - private set(value) { - val coercedValue = value.coerceIn(0, maxExpansionWidth) - if (value == currentDraggingOffsetState) { - return - } - currentDraggingOffsetState = coercedValue - currentMeasuredDraggingOffset = coercedValue - } - - internal var isDragging by mutableStateOf(false) - private set - - internal var isSettling by mutableStateOf(false) - private set - - internal val isDraggingOrSettling - get() = isDragging || isSettling - - @VisibleForTesting - internal var maxExpansionWidth = 0 - 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 - private set - - private var anchorPositions: IntList = emptyIntList() - - private val dragScope: DragScope = - object : DragScope { - override fun dragBy(pixels: Float): Unit = dispatchRawDelta(pixels) - } - - private val dragMutex = MutatorMutex() - - fun isUnspecified(): Boolean = - firstPaneWidthState == UnspecifiedWidth && - firstPanePercentage.isNaN() && - currentDraggingOffset == UnspecifiedWidth - - override fun dispatchRawDelta(delta: Float) { - if (currentMeasuredDraggingOffset == UnspecifiedWidth) { - return - } - currentDraggingOffset = (currentMeasuredDraggingOffset + delta).toInt() - } - - override suspend fun drag(dragPriority: MutatePriority, block: suspend DragScope.() -> Unit) = - coroutineScope { - isDragging = true - dragMutex.mutateWith(dragScope, dragPriority, block) - isDragging = false - } - - internal fun onMeasured(measuredWidth: Int, density: Density) { - if (measuredWidth == maxExpansionWidth) { - return - } - maxExpansionWidth = measuredWidth - if (firstPaneWidth != UnspecifiedWidth) { - firstPaneWidth = firstPaneWidth - } - anchorPositions = anchors.toPositions(measuredWidth, density) - } - - internal fun onExpansionOffsetMeasured(measuredOffset: Int) { - currentMeasuredDraggingOffset = measuredOffset - } - - internal suspend fun settleToAnchorIfNeeded(velocity: Float) { - val currentAnchorPositions = anchorPositions - if (currentAnchorPositions.isEmpty()) { - return - } - dragMutex.mutate(MutatePriority.PreventUserInput) { - isSettling = true - // TODO(conradchen): Use the right animation spec here. - animate( - currentMeasuredDraggingOffset.toFloat(), - currentAnchorPositions - .getPositionOfTheClosestAnchor(currentMeasuredDraggingOffset, velocity) - .toFloat(), - velocity, - ) { value, _ -> - currentDraggingOffset = value.toInt() - } - isSettling = false - } - } - - private fun IntList.getPositionOfTheClosestAnchor(currentPosition: Int, velocity: Float): Int = - minBy( - when { - velocity >= AnchoringVelocityThreshold -> { - { anchorPosition: Int -> - val delta = anchorPosition - currentPosition - if (delta < 0) Int.MAX_VALUE else delta - } - } - velocity <= -AnchoringVelocityThreshold -> { - { anchorPosition: Int -> - val delta = currentPosition - anchorPosition - if (delta < 0) Int.MAX_VALUE else delta - } - } - else -> { - { anchorPosition: Int -> abs(currentPosition - anchorPosition) } - } - } - ) - - companion object { - const val UnspecifiedWidth = -1 - private const val AnchoringVelocityThreshold = 200F - } -} - -@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) - - constructor(startOffset: Dp) : this(Int.MIN_VALUE, startOffset) -} - -@OptIn(ExperimentalMaterial3AdaptiveApi::class) -private fun List.toPositions( - maxExpansionWidth: Int, - density: Density, -): 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) - } - } - // TODO https://youtrack.jetbrains.com/issue/KT-70005 - if (anchors.isEmpty()) return anchors - anchors.sort() - return anchors -} - -private fun > IntList.minBy(selector: (Int) -> T): Int { - if (isEmpty()) { - throw NoSuchElementException() - } - var minElem = this[0] - var minValue = selector(minElem) - for (i in 1 until size) { - val elem = this[i] - val value = selector(elem) - if (minValue > value) { - minElem = elem - minValue = value - } - } - return minElem -} diff --git a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneMotion.kt b/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneMotion.kt deleted file mode 100644 index 241e160..0000000 --- a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneMotion.kt +++ /dev/null @@ -1,254 +0,0 @@ -/* - * 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.compose.material3.adaptive.layout - -import androidx.annotation.VisibleForTesting -import androidx.compose.animation.EnterTransition -import androidx.compose.animation.ExitTransition -import androidx.compose.animation.core.FiniteAnimationSpec -import androidx.compose.animation.expandHorizontally -import androidx.compose.animation.shrinkHorizontally -import androidx.compose.animation.slideInHorizontally -import androidx.compose.animation.slideOutHorizontally -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.LookaheadScope -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.IntSize -import kotlin.jvm.JvmInline -import kotlin.math.max -import kotlin.math.min - -@ExperimentalMaterial3AdaptiveApi -internal interface PaneMotionScope : LookaheadScope { - val positionAnimationSpec: FiniteAnimationSpec - val sizeAnimationSpec: FiniteAnimationSpec - val delayedPositionAnimationSpec: FiniteAnimationSpec - val slideInFromLeftOffset: Int - val slideInFromRightOffset: Int - val slideOutToLeftOffset: Int - val slideOutToRightOffset: Int - val motionProgress: () -> Float -} - -@ExperimentalMaterial3AdaptiveApi -internal interface PaneMotion { - val PaneMotionScope.enterTransition: EnterTransition - val PaneMotionScope.exitTransition: ExitTransition - val PaneMotionScope.animateBoundsModifier: Modifier -} - -@ExperimentalMaterial3AdaptiveApi -@JvmInline -internal value class DefaultPaneMotion private constructor(val value: Int) : PaneMotion { - companion object { - val NoMotion = DefaultPaneMotion(0) - val AnimateBounds = DefaultPaneMotion(1) - val EnterFromLeft = DefaultPaneMotion(2) - val EnterFromRight = DefaultPaneMotion(3) - val EnterFromLeftDelayed = DefaultPaneMotion(4) - val EnterFromRightDelayed = DefaultPaneMotion(5) - val ExitToLeft = DefaultPaneMotion(6) - val ExitToRight = DefaultPaneMotion(7) - val EnterWithExpand = DefaultPaneMotion(8) - val ExitWithShrink = DefaultPaneMotion(9) - } - - override val PaneMotionScope.enterTransition: EnterTransition - get() = - when (this@DefaultPaneMotion) { - EnterFromLeft -> - slideInHorizontally(positionAnimationSpec) { slideInFromLeftOffset } - EnterFromRight -> - slideInHorizontally(positionAnimationSpec) { slideInFromRightOffset } - EnterFromLeftDelayed -> - slideInHorizontally(delayedPositionAnimationSpec) { slideInFromLeftOffset } - EnterFromRightDelayed -> - slideInHorizontally(delayedPositionAnimationSpec) { slideInFromRightOffset } - // TODO(conradche): Figure out how to expand with position change - EnterWithExpand -> - expandHorizontally(sizeAnimationSpec, Alignment.CenterHorizontally) - else -> EnterTransition.None - } - - override val PaneMotionScope.exitTransition: ExitTransition - get() = - when (this@DefaultPaneMotion) { - ExitToLeft -> slideOutHorizontally(positionAnimationSpec) { slideOutToLeftOffset } - ExitToRight -> slideOutHorizontally(positionAnimationSpec) { slideOutToRightOffset } - // TODO(conradche): Figure out how to shrink with position change - ExitWithShrink -> - shrinkHorizontally(sizeAnimationSpec, Alignment.CenterHorizontally) - else -> ExitTransition.None - } - - override val PaneMotionScope.animateBoundsModifier: Modifier - get() = - Modifier.animateBounds( - motionProgress, - sizeAnimationSpec, - positionAnimationSpec, - this, - this@DefaultPaneMotion == AnimateBounds - ) - - override fun toString(): String = - when (this) { - NoMotion -> "NoMotion" - AnimateBounds -> "AnimateBounds" - EnterFromLeft -> "EnterFromLeft" - EnterFromRight -> "EnterFromRight" - EnterFromLeftDelayed -> "EnterFromLeftDelayed" - EnterFromRightDelayed -> "EnterFromRightDelayed" - ExitToLeft -> "ExitToLeft" - ExitToRight -> "ExitToRight" - EnterWithExpand -> "EnterWithExpand" - ExitWithShrink -> "ExitWithShrink" - else -> "Undefined($value)" - } -} - -@ExperimentalMaterial3AdaptiveApi -@VisibleForTesting -internal fun calculatePaneMotion( - previousScaffoldValue: PaneScaffoldValue, - currentScaffoldValue: PaneScaffoldValue, - paneOrder: PaneScaffoldHorizontalOrder, -): Array { - val numOfPanes = paneOrder.size - val paneStatus = Array(numOfPanes) { PaneMotionStatus.Hidden } - val paneMotions = Array(numOfPanes) { DefaultPaneMotion.NoMotion } - var firstShownPaneIndex = numOfPanes - var firstEnteringPaneIndex = numOfPanes - var lastShownPaneIndex = -1 - var lastEnteringPaneIndex = -1 - // First pass, to decide the entering/exiting status of each pane, and collect info for - // deciding, given a certain pane, if there's a pane on its left or on its right that is - // entering or keep showing during the transition. - // Also set up the motions of all panes that keep showing to AnimateBounds. - paneOrder.forEachIndexed { i, role -> - paneStatus[i] = - PaneMotionStatus.calculate(previousScaffoldValue[role], currentScaffoldValue[role]) - when (paneStatus[i]) { - PaneMotionStatus.Shown -> { - firstShownPaneIndex = min(firstShownPaneIndex, i) - lastShownPaneIndex = max(lastShownPaneIndex, i) - paneMotions[i] = DefaultPaneMotion.AnimateBounds - } - PaneMotionStatus.Entering -> { - firstEnteringPaneIndex = min(firstEnteringPaneIndex, i) - lastEnteringPaneIndex = max(lastEnteringPaneIndex, i) - } - } - } - // Second pass, to decide the exiting motions of all exiting panes. - // Also collects info for the next pass to decide the entering motions of entering panes. - var hasPanesExitToRight = false - var hasPanesExitToLeft = false - var firstPaneExitToRightIndex = numOfPanes - var lastPaneExitToLeftIndex = -1 - paneOrder.forEachIndexed { i, _ -> - val hasShownPanesOnLeft = firstShownPaneIndex < i - val hasEnteringPanesOnLeft = firstEnteringPaneIndex < i - val hasShownPanesOnRight = lastShownPaneIndex > i - val hasEnteringPanesOnRight = lastEnteringPaneIndex > i - if (paneStatus[i] == PaneMotionStatus.Exiting) { - paneMotions[i] = - if (!hasShownPanesOnRight && !hasEnteringPanesOnRight) { - // No panes will interfere the motion on the right, exit to right. - hasPanesExitToRight = true - firstPaneExitToRightIndex = min(firstPaneExitToRightIndex, i) - DefaultPaneMotion.ExitToRight - } else if (!hasShownPanesOnLeft && !hasEnteringPanesOnLeft) { - // No panes will interfere the motion on the left, exit to left. - hasPanesExitToLeft = true - lastPaneExitToLeftIndex = max(lastPaneExitToLeftIndex, i) - DefaultPaneMotion.ExitToLeft - } else if (!hasShownPanesOnRight) { - // Only showing panes can interfere the motion on the right, exit to right. - hasPanesExitToRight = true - firstPaneExitToRightIndex = min(firstPaneExitToRightIndex, i) - DefaultPaneMotion.ExitToRight - } else if (!hasShownPanesOnLeft) { // Only showing panes on left - // Only showing panes can interfere the motion on the left, exit to left. - hasPanesExitToLeft = true - lastPaneExitToLeftIndex = max(lastPaneExitToLeftIndex, i) - DefaultPaneMotion.ExitToLeft - } else { - // Both sides has panes that keep being visible during transition, shrink to - // exit - DefaultPaneMotion.ExitWithShrink - } - } - } - // Third pass, to decide the entering motions of all entering panes. - paneOrder.forEachIndexed { i, _ -> - val hasShownPanesOnLeft = firstShownPaneIndex < i - val hasShownPanesOnRight = lastShownPaneIndex > i - val hasLeftPaneExitToRight = firstPaneExitToRightIndex < i - val hasRightPaneExitToLeft = lastPaneExitToLeftIndex > i - // For a given pane, if there's another pane that keeps showing on its right, or there's - // a pane on its right that's exiting to its left, the pane cannot enter from right since - // doing so will either interfere with the showing pane, or cause incorrect order of the - // pane position during the transition. In other words, this case is considered "blocking". - // Same on the other side. - val noBlockingPanesOnRight = !hasShownPanesOnRight && !hasRightPaneExitToLeft - val noBlockingPanesOnLeft = !hasShownPanesOnLeft && !hasLeftPaneExitToRight - if (paneStatus[i] == PaneMotionStatus.Entering) { - paneMotions[i] = - if (noBlockingPanesOnRight && !hasPanesExitToRight) { - // No panes will block the motion on the right, enter from right. - DefaultPaneMotion.EnterFromRight - } else if (noBlockingPanesOnLeft && !hasPanesExitToLeft) { - // No panes will block the motion on the left, enter from left. - DefaultPaneMotion.EnterFromLeft - } else if (noBlockingPanesOnRight) { - // Only hiding panes can interfere the motion on the right, enter from right. - DefaultPaneMotion.EnterFromRightDelayed - } else if (noBlockingPanesOnLeft) { - // Only hiding panes can interfere the motion on the left, enter from left. - DefaultPaneMotion.EnterFromLeftDelayed - } else { - // Both sides has panes that keep being visible during transition, expand to - // enter - DefaultPaneMotion.EnterWithExpand - } - } - } - return paneMotions -} - -@OptIn(ExperimentalMaterial3AdaptiveApi::class) -@JvmInline -private value class PaneMotionStatus private constructor(val value: Int) { - companion object { - val Hidden = PaneMotionStatus(0) - val Exiting = PaneMotionStatus(1) - val Entering = PaneMotionStatus(2) - val Shown = PaneMotionStatus(3) - - fun calculate( - previousValue: PaneAdaptedValue, - currentValue: PaneAdaptedValue, - ): PaneMotionStatus { - val wasShown = if (previousValue == PaneAdaptedValue.Hidden) 0 else 1 - val isShown = if (currentValue == PaneAdaptedValue.Hidden) 0 else 2 - return PaneMotionStatus(wasShown or isShown) - } - } -} diff --git a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffold.kt b/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffold.kt deleted file mode 100644 index 2a403ea..0000000 --- a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffold.kt +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2023 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.compose.material3.adaptive.layout - -import androidx.compose.ui.Modifier -import androidx.compose.ui.node.ModifierNodeElement -import androidx.compose.ui.node.ParentDataModifierNode -import androidx.compose.ui.platform.InspectorInfo -import androidx.compose.ui.platform.debugInspectorInfo -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp - -/** Scope for the panes of pane scaffolds. */ -sealed interface PaneScaffoldScope { - /** - * This modifier specifies the preferred width for a pane, and the pane scaffold implementation - * will try its best to respect this width when the associated pane is rendered as a fixed pane, - * i.e., a pane that are not stretching to fill the remaining spaces. In case the modifier is - * not set or set to [Dp.Unspecified], the default preferred widths provided by - * [PaneScaffoldDirective] are supposed to be used. - * - * @see PaneScaffoldDirective.defaultPanePreferredWidth - */ - fun Modifier.preferredWidth(width: Dp): Modifier -} - -internal abstract class PaneScaffoldScopeImpl : PaneScaffoldScope { - override fun Modifier.preferredWidth(width: Dp): Modifier { - require(width == Dp.Unspecified || width > 0.dp) { "invalid width" } - return this.then(PreferredWidthElement(width)) - } -} - -private class PreferredWidthElement( - private val width: Dp, -) : ModifierNodeElement() { - private val inspectorInfo = debugInspectorInfo { - name = "preferredWidth" - value = width - } - - override fun create(): PreferredWidthNode { - return PreferredWidthNode(width) - } - - override fun update(node: PreferredWidthNode) { - node.width = width - } - - override fun InspectorInfo.inspectableProperties() { - inspectorInfo() - } - - override fun hashCode(): Int { - return width.hashCode() - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - val otherModifier = other as? PreferredWidthElement ?: return false - return width == otherModifier.width - } -} - -private class PreferredWidthNode(var width: Dp) : ParentDataModifierNode, Modifier.Node() { - override fun Density.modifyParentData(parentData: Any?) = - ((parentData as? PaneScaffoldParentData) ?: PaneScaffoldParentData()).also { - it.preferredWidth = with(this) { width.toPx() } - } -} - -internal fun Modifier.animatedPane(): Modifier { - return this.then(AnimatedPaneElement) -} - -private object AnimatedPaneElement : ModifierNodeElement() { - private val inspectorInfo = debugInspectorInfo { - name = "isPaneComposable" - value = true - } - - override fun create(): AnimatedPaneNode { - return AnimatedPaneNode() - } - - override fun update(node: AnimatedPaneNode) {} - - override fun InspectorInfo.inspectableProperties() { - inspectorInfo() - } - - override fun hashCode(): Int { - return 0 - } - - override fun equals(other: Any?): Boolean { - return (other is AnimatedPaneElement) - } -} - -private class AnimatedPaneNode : ParentDataModifierNode, Modifier.Node() { - override fun Density.modifyParentData(parentData: Any?) = - ((parentData as? PaneScaffoldParentData) ?: PaneScaffoldParentData()).also { - it.isAnimatedPane = true - } -} - -internal data class PaneScaffoldParentData( - var preferredWidth: Float? = null, - var isAnimatedPane: Boolean = false, -) diff --git a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirective.kt b/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirective.kt deleted file mode 100644 index 30a26ae..0000000 --- a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldDirective.kt +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright 2023 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.compose.material3.adaptive.layout - -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi -import androidx.compose.material3.adaptive.Posture -import androidx.compose.material3.adaptive.WindowAdaptiveInfo -import androidx.compose.material3.adaptive.allVerticalHingeBounds -import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo -import androidx.compose.material3.adaptive.occludingVerticalHingeBounds -import androidx.compose.material3.adaptive.separatingVerticalHingeBounds -import androidx.compose.runtime.Immutable -import androidx.compose.ui.geometry.Rect -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.window.core.layout.WindowWidthSizeClass - -/** - * Calculates the recommended [PaneScaffoldDirective] from a given [WindowAdaptiveInfo]. Use this - * method with [currentWindowAdaptiveInfo] to acquire Material-recommended adaptive layout settings - * of the current activity window. - * - * See more details on the [Material design guideline site] - * (https://m3.material.io/foundations/layout/applying-layout/window-size-classes). - * - * @param windowAdaptiveInfo [WindowAdaptiveInfo] that collects useful information in making layout - * adaptation decisions like [WindowSizeClass]. - * @param verticalHingePolicy [HingePolicy] that decides how layouts are supposed to address - * vertical hinges. - * @return an [PaneScaffoldDirective] to be used to decide adaptive layout states. - */ -@ExperimentalMaterial3AdaptiveApi -fun calculatePaneScaffoldDirective( - windowAdaptiveInfo: WindowAdaptiveInfo, - verticalHingePolicy: HingePolicy = HingePolicy.AvoidSeparating, -): PaneScaffoldDirective { - val maxHorizontalPartitions: Int - val horizontalPartitionSpacerSize: Dp - when (windowAdaptiveInfo.windowSizeClass.windowWidthSizeClass) { - WindowWidthSizeClass.COMPACT -> { - maxHorizontalPartitions = 1 - horizontalPartitionSpacerSize = 0.dp - } - WindowWidthSizeClass.MEDIUM -> { - maxHorizontalPartitions = 1 - horizontalPartitionSpacerSize = 0.dp - } - else -> { - maxHorizontalPartitions = 2 - horizontalPartitionSpacerSize = 24.dp - } - } - val maxVerticalPartitions: Int - val verticalPartitionSpacerSize: Dp - - // TODO(conradchen): Confirm the table top mode settings - if (windowAdaptiveInfo.windowPosture.isTabletop) { - maxVerticalPartitions = 2 - verticalPartitionSpacerSize = 24.dp - } else { - maxVerticalPartitions = 1 - verticalPartitionSpacerSize = 0.dp - } - - // TODO(conradchen): add 412.dp for L/XL window size class when they are available - val defaultPanePreferredWidth = 360.dp - - return PaneScaffoldDirective( - maxHorizontalPartitions, - horizontalPartitionSpacerSize, - maxVerticalPartitions, - verticalPartitionSpacerSize, - defaultPanePreferredWidth, - getExcludedVerticalBounds(windowAdaptiveInfo.windowPosture, verticalHingePolicy) - ) -} - -/** - * Calculates the recommended [PaneScaffoldDirective] from a given [WindowAdaptiveInfo]. Use this - * method with [currentWindowAdaptiveInfo] to acquire Material-recommended dense-mode adaptive - * layout settings of the current activity window. Note that this function results in a dual-pane - * layout when the [WindowWidthSizeClass] is [WindowWidthSizeClass.MEDIUM], while - * [calculatePaneScaffoldDirective] results in a single-pane layout instead. We recommend to use - * [calculatePaneScaffoldDirective], unless you have a strong use case to show two panes on a - * medium-width window, which can make your layout look too packed. - * - * See more details on the [Material design guideline site] - * (https://m3.material.io/foundations/layout/applying-layout/window-size-classes). - * - * @param windowAdaptiveInfo [WindowAdaptiveInfo] that collects useful information in making layout - * adaptation decisions like [WindowSizeClass]. - * @param verticalHingePolicy [HingePolicy] that decides how layouts are supposed to address - * vertical hinges. - * @return an [PaneScaffoldDirective] to be used to decide adaptive layout states. - */ -@ExperimentalMaterial3AdaptiveApi -fun calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth( - windowAdaptiveInfo: WindowAdaptiveInfo, - verticalHingePolicy: HingePolicy = HingePolicy.AvoidSeparating, -): PaneScaffoldDirective { - val isMediumWidth = - windowAdaptiveInfo.windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.MEDIUM - return with(calculatePaneScaffoldDirective(windowAdaptiveInfo, verticalHingePolicy)) { - copy( - maxHorizontalPartitions = if (isMediumWidth) 2 else maxHorizontalPartitions, - horizontalPartitionSpacerSize = - if (isMediumWidth) { - 24.dp - } else { - horizontalPartitionSpacerSize - } - ) - } -} - -private fun getExcludedVerticalBounds(posture: Posture, hingePolicy: HingePolicy): List { - return when (hingePolicy) { - HingePolicy.AvoidSeparating -> posture.separatingVerticalHingeBounds - HingePolicy.AvoidOccluding -> posture.occludingVerticalHingeBounds - HingePolicy.AlwaysAvoid -> posture.allVerticalHingeBounds - else -> emptyList() - } -} - -/** - * Top-level directives about how a pane scaffold should be arranged and spaced, like how many - * partitions the layout can be split into and what should be the gutter size. - * - * @property maxHorizontalPartitions the max number of partitions along the horizontal axis the - * layout can be split into. - * @property horizontalPartitionSpacerSize Size of the spacers between horizontal partitions. It's - * equivalent to the left/right margins the horizontal partitions. - * @property maxVerticalPartitions the max number of partitions along the vertical axis the layout - * can be split into. - * @property verticalPartitionSpacerSize Size of the spacers between vertical partitions. It's - * equivalent to the top/bottom margins of the vertical partitions. - * @property defaultPanePreferredWidth Default preferred width of panes that will be used by the - * scaffold if there's no [Modifier.preferredWidth] provided with a pane. See - * [Modifier.preferredWidth] for more info about how and when preferred width will be used. - * @property excludedBounds the bounds of all areas in the window that the layout needs to avoid - * displaying anything upon it. Usually these bounds represent where physical hinges are. - */ -@Immutable -class PaneScaffoldDirective( - val maxHorizontalPartitions: Int, - val horizontalPartitionSpacerSize: Dp, - val maxVerticalPartitions: Int, - val verticalPartitionSpacerSize: Dp, - val defaultPanePreferredWidth: Dp, - val excludedBounds: List, -) { - /** - * Returns a new copy of [PaneScaffoldDirective] with specified fields overwritten. Use this - * method to create a custom [PaneScaffoldDirective] from the default instance or the result of - * [calculatePaneScaffoldDirective]. - * - * @param maxHorizontalPartitions the max number of partitions along the horizontal axis the - * layout can be split into. - * @param horizontalPartitionSpacerSize Size of the spacers between horizontal partitions. It's - * equivalent to the left/right margins the horizontal partitions. - * @param maxVerticalPartitions the max number of partitions along the vertical axis the layout - * can be split into. - * @param verticalPartitionSpacerSize Size of the spacers between vertical partitions. It's - * equivalent to the top/bottom margins of the vertical partitions. - * @param defaultPanePreferredWidth Default preferred width of panes that will be used by the - * scaffold if there's no [Modifier.preferredWidth] provided with a pane. - * @param excludedBounds the bounds of all areas in the window that the layout needs to avoid - * displaying anything upon it. Usually these bounds represent where physical hinges are. - */ - fun copy( - maxHorizontalPartitions: Int = this.maxHorizontalPartitions, - horizontalPartitionSpacerSize: Dp = this.horizontalPartitionSpacerSize, - maxVerticalPartitions: Int = this.maxVerticalPartitions, - verticalPartitionSpacerSize: Dp = this.verticalPartitionSpacerSize, - defaultPanePreferredWidth: Dp = this.defaultPanePreferredWidth, - excludedBounds: List = this.excludedBounds, - ): PaneScaffoldDirective = - PaneScaffoldDirective( - maxHorizontalPartitions, - horizontalPartitionSpacerSize, - maxVerticalPartitions, - verticalPartitionSpacerSize, - defaultPanePreferredWidth, - excludedBounds - ) - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is PaneScaffoldDirective) return false - if (maxHorizontalPartitions != other.maxHorizontalPartitions) return false - if (horizontalPartitionSpacerSize != other.horizontalPartitionSpacerSize) return false - if (maxVerticalPartitions != other.maxVerticalPartitions) return false - if (verticalPartitionSpacerSize != other.verticalPartitionSpacerSize) return false - if (defaultPanePreferredWidth != other.defaultPanePreferredWidth) return false - if (excludedBounds != other.excludedBounds) return false - return true - } - - override fun hashCode(): Int { - var result = maxHorizontalPartitions - result = 31 * result + horizontalPartitionSpacerSize.hashCode() - result = 31 * result + maxVerticalPartitions - result = 31 * result + verticalPartitionSpacerSize.hashCode() - result = 31 * result + defaultPanePreferredWidth.hashCode() - result = 31 * result + excludedBounds.hashCode() - return result - } - - override fun toString(): String { - return "PaneScaffoldDirective(maxHorizontalPartitions=$maxHorizontalPartitions, " + - "horizontalPartitionSpacerSize=$horizontalPartitionSpacerSize, " + - "maxVerticalPartitions=$maxVerticalPartitions, " + - "verticalPartitionSpacerSize=$verticalPartitionSpacerSize, " + - "defaultPanePreferredWidth=$defaultPanePreferredWidth, " + - "number of excluded bounds=${excludedBounds.size})" - } - - companion object { - /** - * A default instance of [PaneScaffoldDirective] that suggests a single-pane layout that - * occupies the full window. To create a customized [PaneScaffoldDirective], you can use - * [PaneScaffoldDirective.copy] on the default instance to create a copy with custom values. - */ - val Default = - PaneScaffoldDirective( - maxHorizontalPartitions = 1, - horizontalPartitionSpacerSize = 0.dp, - maxVerticalPartitions = 1, - verticalPartitionSpacerSize = 0.dp, - defaultPanePreferredWidth = 360.dp, - excludedBounds = emptyList() - ) - } -} - -/** Policies that indicate how hinges are supposed to be addressed in an adaptive layout. */ -@Immutable -@kotlin.jvm.JvmInline -value class HingePolicy private constructor(private val value: Int) { - override fun toString(): String { - return "HingePolicy." + - when (this) { - AlwaysAvoid -> "AlwaysAvoid" - AvoidSeparating -> "AvoidOccludingAndSeparating" - AvoidOccluding -> "AvoidOccludingOnly" - NeverAvoid -> "NeverAvoid" - else -> "" - } - } - - companion object { - /** When rendering content in a layout, always avoid where hinges are. */ - val AlwaysAvoid = HingePolicy(0) - - /** - * When rendering content in a layout, avoid hinges that are separating. Note that an - * occluding hinge is supposed to be separating as well but not vice versa. - */ - val AvoidSeparating = HingePolicy(1) - - /** - * When rendering content in a layout, avoid hinges that are occluding. Note that an - * occluding hinge is supposed to be separating as well but not vice versa. - */ - val AvoidOccluding = HingePolicy(2) - - /** When rendering content in a layout, never avoid any hinges, separating or not. */ - val NeverAvoid = HingePolicy(3) - } -} diff --git a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldHorizontalOrder.kt b/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldHorizontalOrder.kt deleted file mode 100644 index 0d915a2..0000000 --- a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldHorizontalOrder.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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.compose.material3.adaptive.layout - -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi - -@ExperimentalMaterial3AdaptiveApi -internal interface PaneScaffoldHorizontalOrder { - val size: Int - - fun indexOf(role: T): Int - - fun forEach(action: (T) -> Unit) - - fun forEachIndexed(action: (Int, T) -> Unit) - - fun forEachIndexedReversed(action: (Int, T) -> Unit) -} diff --git a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldValue.kt b/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldValue.kt deleted file mode 100644 index d3b1d1f..0000000 --- a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneScaffoldValue.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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.compose.material3.adaptive.layout - -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi - -@ExperimentalMaterial3AdaptiveApi -internal interface PaneScaffoldValue { - operator fun get(role: T): PaneAdaptedValue -} diff --git a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt b/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt deleted file mode 100644 index 2c5acfa..0000000 --- a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2023 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.compose.material3.adaptive.layout - -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier - -/** - * A Material opinionated implementation of [ThreePaneScaffold] that will display the provided three - * panes in a canonical supporting-pane layout. - * - * @param directive The top-level directives about how the scaffold should arrange its panes. - * @param value The current adapted value of the scaffold, which indicates how each pane of the - * scaffold is adapted. - * @param mainPane the main pane of the scaffold, which is supposed to hold the major content of an - * app, for example, the editing screen of a doc app. See [SupportingPaneScaffoldRole.Main]. - * @param supportingPane the supporting pane of the scaffold, which is supposed to hold the support - * content of an app, for example, the comment list of a doc app. See - * [SupportingPaneScaffoldRole.Supporting]. - * @param modifier [Modifier] of the scaffold layout. - * @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]. - */ -@ExperimentalMaterial3AdaptiveApi -@Composable -fun SupportingPaneScaffold( - directive: PaneScaffoldDirective, - value: ThreePaneScaffoldValue, - mainPane: @Composable ThreePaneScaffoldScope.() -> Unit, - supportingPane: @Composable ThreePaneScaffoldScope.() -> Unit, - modifier: Modifier = Modifier, - extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null, -) { - ThreePaneScaffold( - modifier = modifier.fillMaxSize(), - scaffoldDirective = directive, - scaffoldValue = value, - paneOrder = SupportingPaneScaffoldDefaults.PaneOrder, - secondaryPane = supportingPane, - tertiaryPane = extraPane, - primaryPane = mainPane - ) -} - -/** Provides default values of [SupportingPaneScaffold]. */ -@ExperimentalMaterial3AdaptiveApi -object SupportingPaneScaffoldDefaults { - /** - * Creates a default [ThreePaneScaffoldAdaptStrategies] for [SupportingPaneScaffold]. - * - * @param mainPaneAdaptStrategy the adapt strategy of the main pane - * @param supportingPaneAdaptStrategy the adapt strategy of the supporting pane - * @param extraPaneAdaptStrategy the adapt strategy of the extra pane - */ - fun adaptStrategies( - mainPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide, - supportingPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide, - extraPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide, - ): ThreePaneScaffoldAdaptStrategies = - ThreePaneScaffoldAdaptStrategies( - mainPaneAdaptStrategy, - supportingPaneAdaptStrategy, - extraPaneAdaptStrategy - ) - - /** - * Denotes [ThreePaneScaffold] to use the supporting-pane pane-order to arrange its panes - * horizontally, which allocates panes in the order of primary, secondary, and tertiary from - * start to end. - */ - internal val PaneOrder = - ThreePaneScaffoldHorizontalOrder( - ThreePaneScaffoldRole.Primary, - ThreePaneScaffoldRole.Secondary, - ThreePaneScaffoldRole.Tertiary - ) -} - -/** - * The set of the available pane roles of [SupportingPaneScaffold]. Those roles map to their - * corresponding [ThreePaneScaffoldRole], which is a generic role definition across all types of - * three pane scaffolds. We suggest you to use the values defined here instead of the raw - * [ThreePaneScaffoldRole] under the context of [SupportingPaneScaffold] for better code clarity. - */ -@ExperimentalMaterial3AdaptiveApi -object SupportingPaneScaffoldRole { - /** - * The main pane of [SupportingPaneScaffold], which is supposed to hold the major content of an - * app, for example, the editing screen of a doc app. It maps to - * [ThreePaneScaffoldRole.Primary]. - */ - val Main = ThreePaneScaffoldRole.Primary - - /** - * The supporting pane of [SupportingPaneScaffold], which is supposed to hold the support - * content of an app, for example, the comment list of a doc app. It maps to - * [ThreePaneScaffoldRole.Secondary]. - */ - val Supporting = ThreePaneScaffoldRole.Secondary - - /** - * The extra pane of [SupportingPaneScaffold], which is supposed to hold any additional content - * besides the main and the supporting panes, for example, a styling panel in a doc app. It maps - * to [ThreePaneScaffoldRole.Tertiary]. - */ - val Extra = ThreePaneScaffoldRole.Tertiary -} diff --git a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotion.kt b/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotion.kt deleted file mode 100644 index b09fa45..0000000 --- a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotion.kt +++ /dev/null @@ -1,436 +0,0 @@ -/* - * 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.compose.material3.adaptive.layout - -import androidx.compose.animation.EnterTransition -import androidx.compose.animation.ExitTransition -import androidx.compose.animation.core.AnimationVector -import androidx.compose.animation.core.FiniteAnimationSpec -import androidx.compose.animation.core.Spring -import androidx.compose.animation.core.SpringSpec -import androidx.compose.animation.core.TwoWayConverter -import androidx.compose.animation.core.VectorizedFiniteAnimationSpec -import androidx.compose.animation.core.VisibilityThreshold -import androidx.compose.animation.core.snap -import androidx.compose.animation.core.spring -import androidx.compose.animation.slideInHorizontally -import androidx.compose.animation.slideOutHorizontally -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi -import androidx.compose.runtime.Immutable -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.IntSize -import kotlin.jvm.JvmStatic - -/** Holds the transitions that can be applied to the different panes. */ -@ExperimentalMaterial3AdaptiveApi -@Immutable -internal open class ThreePaneMotion -internal constructor( - internal val positionAnimationSpec: FiniteAnimationSpec = snap(), - internal val sizeAnimationSpec: FiniteAnimationSpec = snap(), - private val firstPaneEnterTransition: EnterTransition = EnterTransition.None, - private val firstPaneExitTransition: ExitTransition = ExitTransition.None, - private val secondPaneEnterTransition: EnterTransition = EnterTransition.None, - private val secondPaneExitTransition: ExitTransition = ExitTransition.None, - private val thirdPaneEnterTransition: EnterTransition = EnterTransition.None, - private val thirdPaneExitTransition: ExitTransition = ExitTransition.None, -) { - - /** - * Resolves and returns the [EnterTransition] for the given [ThreePaneScaffoldRole] at the given - * [ThreePaneScaffoldHorizontalOrder]. - */ - fun enterTransition( - role: ThreePaneScaffoldRole, - paneOrder: ThreePaneScaffoldHorizontalOrder, - ): EnterTransition { - // Quick return in case this instance is the NoMotion one. - if (this === NoMotion) return EnterTransition.None - - return when (paneOrder.indexOf(role)) { - 0 -> firstPaneEnterTransition - 1 -> secondPaneEnterTransition - else -> thirdPaneEnterTransition - } - } - - /** - * Resolves and returns the [ExitTransition] for the given [ThreePaneScaffoldRole] at the given - * [ThreePaneScaffoldHorizontalOrder]. - */ - fun exitTransition( - role: ThreePaneScaffoldRole, - paneOrder: ThreePaneScaffoldHorizontalOrder, - ): ExitTransition { - // Quick return in case this instance is the NoMotion one. - if (this === NoMotion) return ExitTransition.None - - return when (paneOrder.indexOf(role)) { - 0 -> firstPaneExitTransition - 1 -> secondPaneExitTransition - else -> thirdPaneExitTransition - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is ThreePaneMotion) return false - if (this.positionAnimationSpec != other.positionAnimationSpec) return false - if (this.sizeAnimationSpec != other.sizeAnimationSpec) return false - if (this.firstPaneEnterTransition != other.firstPaneEnterTransition) return false - if (this.firstPaneExitTransition != other.firstPaneExitTransition) return false - if (this.secondPaneEnterTransition != other.secondPaneEnterTransition) return false - if (this.secondPaneExitTransition != other.secondPaneExitTransition) return false - if (this.thirdPaneEnterTransition != other.thirdPaneEnterTransition) return false - if (this.thirdPaneExitTransition != other.thirdPaneExitTransition) return false - return true - } - - override fun hashCode(): Int { - var result = positionAnimationSpec.hashCode() - result = 31 * result + sizeAnimationSpec.hashCode() - result = 31 * result + firstPaneEnterTransition.hashCode() - result = 31 * result + firstPaneExitTransition.hashCode() - result = 31 * result + secondPaneEnterTransition.hashCode() - result = 31 * result + secondPaneExitTransition.hashCode() - result = 31 * result + thirdPaneEnterTransition.hashCode() - result = 31 * result + thirdPaneExitTransition.hashCode() - return result - } - - companion object { - /** - * A ThreePaneMotion with all transitions set to [EnterTransition.None] and - * [ExitTransition.None]. - */ - val NoMotion = ThreePaneMotion() - - @JvmStatic - protected fun slideInFromLeft(spacerSize: Int) = - slideInHorizontally(ThreePaneMotionDefaults.PanePositionAnimationSpec) { - -it - spacerSize - } - - @JvmStatic - protected fun slideInFromLeftDelayed(spacerSize: Int) = - slideInHorizontally(ThreePaneMotionDefaults.PanePositionAnimationSpecDelayed) { - -it - spacerSize - } - - @JvmStatic - protected fun slideInFromRight(spacerSize: Int) = - slideInHorizontally(ThreePaneMotionDefaults.PanePositionAnimationSpec) { - it + spacerSize - } - - @JvmStatic - protected fun slideInFromRightDelayed(spacerSize: Int) = - slideInHorizontally(ThreePaneMotionDefaults.PanePositionAnimationSpecDelayed) { - it + spacerSize - } - - @JvmStatic - protected fun slideOutToLeft(spacerSize: Int) = - slideOutHorizontally(ThreePaneMotionDefaults.PanePositionAnimationSpec) { - -it - spacerSize - } - - @JvmStatic - protected fun slideOutToRight(spacerSize: Int) = - slideOutHorizontally(ThreePaneMotionDefaults.PanePositionAnimationSpec) { - it + spacerSize - } - } -} - -@ExperimentalMaterial3AdaptiveApi -@Immutable -internal class MovePanesToLeftMotion(private val spacerSize: Int) : - ThreePaneMotion( - ThreePaneMotionDefaults.PanePositionAnimationSpec, - ThreePaneMotionDefaults.PaneSizeAnimationSpec, - slideInFromRight(spacerSize), - slideOutToLeft(spacerSize), - slideInFromRight(spacerSize), - slideOutToLeft(spacerSize), - slideInFromRight(spacerSize), - slideOutToLeft(spacerSize) - ) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is MovePanesToLeftMotion) return false - if (this.spacerSize != other.spacerSize) return false - return true - } - - override fun hashCode(): Int { - return spacerSize - } -} - -@ExperimentalMaterial3AdaptiveApi -@Immutable -internal class MovePanesToRightMotion(private val spacerSize: Int) : - ThreePaneMotion( - ThreePaneMotionDefaults.PanePositionAnimationSpec, - ThreePaneMotionDefaults.PaneSizeAnimationSpec, - slideInFromLeft(spacerSize), - slideOutToRight(spacerSize), - slideInFromLeft(spacerSize), - slideOutToRight(spacerSize), - slideInFromLeft(spacerSize), - slideOutToRight(spacerSize) - ) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is MovePanesToRightMotion) return false - if (this.spacerSize != other.spacerSize) return false - return true - } - - override fun hashCode(): Int { - return spacerSize - } -} - -@ExperimentalMaterial3AdaptiveApi -@Immutable -internal class SwitchLeftTwoPanesMotion(private val spacerSize: Int) : - ThreePaneMotion( - ThreePaneMotionDefaults.PanePositionAnimationSpec, - ThreePaneMotionDefaults.PaneSizeAnimationSpec, - slideInFromLeftDelayed(spacerSize), - slideOutToLeft(spacerSize), - slideInFromLeftDelayed(spacerSize), - slideOutToLeft(spacerSize), - EnterTransition.None, - ExitTransition.None - ) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is SwitchLeftTwoPanesMotion) return false - if (this.spacerSize != other.spacerSize) return false - return true - } - - override fun hashCode(): Int { - return spacerSize - } -} - -@ExperimentalMaterial3AdaptiveApi -@Immutable -internal class SwitchRightTwoPanesMotion(private val spacerSize: Int) : - ThreePaneMotion( - ThreePaneMotionDefaults.PanePositionAnimationSpec, - ThreePaneMotionDefaults.PaneSizeAnimationSpec, - EnterTransition.None, - ExitTransition.None, - slideInFromRightDelayed(spacerSize), - slideOutToRight(spacerSize), - slideInFromRightDelayed(spacerSize), - slideOutToRight(spacerSize) - ) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is SwitchRightTwoPanesMotion) return false - if (this.spacerSize != other.spacerSize) return false - return true - } - - override fun hashCode(): Int { - return spacerSize - } -} - -@OptIn(ExperimentalMaterial3AdaptiveApi::class) -internal fun calculateThreePaneMotion( - previousScaffoldValue: ThreePaneScaffoldValue, - currentScaffoldValue: ThreePaneScaffoldValue, - paneOrder: ThreePaneScaffoldHorizontalOrder, - spacerSize: Int, -): ThreePaneMotion { - if (previousScaffoldValue.equals(currentScaffoldValue)) { - return ThreePaneMotion.NoMotion - } - val previousExpandedCount = previousScaffoldValue.expandedCount - val currentExpandedCount = currentScaffoldValue.expandedCount - if (previousExpandedCount != currentExpandedCount) { - // TODO(conradchen): Address this case - return ThreePaneMotion.NoMotion - } - return when (previousExpandedCount) { - 1 -> - when (PaneAdaptedValue.Expanded) { - previousScaffoldValue[paneOrder.firstPane] -> { - MovePanesToLeftMotion(spacerSize) - } - previousScaffoldValue[paneOrder.thirdPane] -> { - MovePanesToRightMotion(spacerSize) - } - currentScaffoldValue[paneOrder.thirdPane] -> { - MovePanesToLeftMotion(spacerSize) - } - else -> { - MovePanesToRightMotion(spacerSize) - } - } - 2 -> - when { - previousScaffoldValue[paneOrder.firstPane] == PaneAdaptedValue.Expanded && - currentScaffoldValue[paneOrder.firstPane] == PaneAdaptedValue.Expanded -> { - // The first pane stays, the right two panes switch - SwitchRightTwoPanesMotion(spacerSize) - } - previousScaffoldValue[paneOrder.thirdPane] == PaneAdaptedValue.Expanded && - currentScaffoldValue[paneOrder.thirdPane] == PaneAdaptedValue.Expanded -> { - // The third pane stays, the left two panes switch - SwitchLeftTwoPanesMotion(spacerSize) - } - - // Implies the second pane stays hereafter - currentScaffoldValue[paneOrder.thirdPane] == PaneAdaptedValue.Expanded -> { - // The third pane shows, all panes move left - MovePanesToLeftMotion(spacerSize) - } - else -> { - // The first pane shows, all panes move right - MovePanesToRightMotion(spacerSize) - } - } - else -> { - // Should not happen - ThreePaneMotion.NoMotion - } - } -} - -internal class DelayedSpringSpec( - dampingRatio: Float = Spring.DampingRatioNoBouncy, - stiffness: Float = Spring.StiffnessMedium, - private val delayedRatio: Float, - visibilityThreshold: T? = null, -) : FiniteAnimationSpec { - private val originalSpringSpec = spring(dampingRatio, stiffness, visibilityThreshold) - - override fun vectorize( - converter: TwoWayConverter, - ): VectorizedFiniteAnimationSpec = - DelayedVectorizedSpringSpec(originalSpringSpec.vectorize(converter), delayedRatio) -} - -private class DelayedVectorizedSpringSpec( - val originalVectorizedSpringSpec: VectorizedFiniteAnimationSpec, - val delayedRatio: Float, -) : VectorizedFiniteAnimationSpec { - var delayedTimeNanos: Long = 0 - var cachedInitialValue: V? = null - var cachedTargetValue: V? = null - var cachedInitialVelocity: V? = null - var cachedOriginalDurationNanos: Long = 0 - - override fun getValueFromNanos( - playTimeNanos: Long, - initialValue: V, - targetValue: V, - initialVelocity: V, - ): V { - updateDelayedTimeNanosIfNeeded(initialValue, targetValue, initialVelocity) - return if (playTimeNanos <= delayedTimeNanos) { - initialValue - } else { - originalVectorizedSpringSpec.getValueFromNanos( - playTimeNanos - delayedTimeNanos, - initialValue, - targetValue, - initialVelocity - ) - } - } - - override fun getVelocityFromNanos( - playTimeNanos: Long, - initialValue: V, - targetValue: V, - initialVelocity: V, - ): V { - updateDelayedTimeNanosIfNeeded(initialValue, targetValue, initialVelocity) - return if (playTimeNanos <= delayedTimeNanos) { - initialVelocity - } else { - originalVectorizedSpringSpec.getVelocityFromNanos( - playTimeNanos - delayedTimeNanos, - initialValue, - targetValue, - initialVelocity - ) - } - } - - override fun getDurationNanos(initialValue: V, targetValue: V, initialVelocity: V): Long { - updateDelayedTimeNanosIfNeeded(initialValue, targetValue, initialVelocity) - return cachedOriginalDurationNanos + delayedTimeNanos - } - - private fun updateDelayedTimeNanosIfNeeded( - initialValue: V, - targetValue: V, - initialVelocity: V, - ) { - if ( - initialValue != cachedInitialValue || - targetValue != cachedTargetValue || - initialVelocity != cachedInitialVelocity - ) { - cachedOriginalDurationNanos = - originalVectorizedSpringSpec.getDurationNanos( - initialValue, - targetValue, - initialVelocity - ) - delayedTimeNanos = (cachedOriginalDurationNanos * delayedRatio).toLong() - } - } -} - -@ExperimentalMaterial3AdaptiveApi -internal object ThreePaneMotionDefaults { - // TODO(conradchen): open this to public when we support motion customization - val PanePositionAnimationSpec: SpringSpec = - spring( - dampingRatio = 0.8f, - stiffness = 600f, - visibilityThreshold = IntOffset.VisibilityThreshold - ) - - // TODO(conradchen): open this to public when we support motion customization - val PanePositionAnimationSpecDelayed: DelayedSpringSpec = - DelayedSpringSpec( - dampingRatio = 0.8f, - stiffness = 600f, - delayedRatio = 0.1f, - visibilityThreshold = IntOffset.VisibilityThreshold - ) - - // TODO(conradchen): open this to public when we support motion customization - val PaneSizeAnimationSpec: SpringSpec = - spring( - dampingRatio = 0.8f, - stiffness = 600f, - visibilityThreshold = IntSize.VisibilityThreshold - ) -} diff --git a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt b/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt deleted file mode 100644 index ccac0cd..0000000 --- a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt +++ /dev/null @@ -1,886 +0,0 @@ -/* - * Copyright 2023 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.compose.material3.adaptive.layout - -import androidx.compose.animation.EnterTransition -import androidx.compose.animation.ExitTransition -import androidx.compose.animation.core.FiniteAnimationSpec -import androidx.compose.animation.core.SeekableTransitionState -import androidx.compose.animation.core.Transition -import androidx.compose.animation.core.rememberTransition -import androidx.compose.animation.core.snap -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -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.geometry.Offset -import androidx.compose.ui.geometry.Rect -import androidx.compose.ui.layout.Layout -import androidx.compose.ui.layout.LookaheadScope -import androidx.compose.ui.layout.Measurable -import androidx.compose.ui.layout.MeasureResult -import androidx.compose.ui.layout.MeasureScope -import androidx.compose.ui.layout.MultiContentMeasurePolicy -import androidx.compose.ui.layout.Placeable -import androidx.compose.ui.layout.boundsInWindow -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.unit.Constraints -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.IntRect -import androidx.compose.ui.unit.IntSize -import androidx.compose.ui.unit.roundToIntRect -import androidx.compose.ui.util.fastForEach -import androidx.compose.ui.util.fastForEachIndexed -import androidx.compose.ui.util.fastMap -import androidx.compose.ui.util.fastMaxOfOrNull -import kotlin.math.max -import kotlin.math.min - -/** - * A pane scaffold composable that can display up to three panes according to the instructions - * provided by [ThreePaneScaffoldValue] in the order that [ThreePaneScaffoldHorizontalOrder] - * specifies, and allocate margins and spacers according to [PaneScaffoldDirective]. - * - * [ThreePaneScaffold] is the base composable functions of adaptive programming. Developers can - * freely pipeline the relevant adaptive signals and use them as input of the scaffold function to - * render the final adaptive layout. - * - * It's recommended to use [ThreePaneScaffold] with [calculatePaneScaffoldDirective], - * [calculateThreePaneScaffoldValue] to follow the Material design guidelines on adaptive - * programming. - * - * @param modifier The modifier to be applied to the layout. - * @param scaffoldDirective The top-level directives about how the scaffold should arrange its - * panes. - * @param scaffoldValue The current adapted value of the scaffold. - * @param paneOrder The horizontal order of the panes from start to end in the scaffold. - * @param secondaryPane The content of the secondary pane that has a priority lower then the primary - * pane but higher than the tertiary pane. - * @param tertiaryPane The content of the tertiary pane that has the lowest priority. - * @param primaryPane The content of the primary pane that has the highest priority. - */ -@ExperimentalMaterial3AdaptiveApi -@Composable -internal fun ThreePaneScaffold( - modifier: Modifier, - scaffoldDirective: PaneScaffoldDirective, - scaffoldValue: ThreePaneScaffoldValue, - paneOrder: ThreePaneScaffoldHorizontalOrder, - secondaryPane: @Composable ThreePaneScaffoldScope.() -> Unit, - tertiaryPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null, - // TODO(conradchen): Moves to use the specific remember function - paneExpansionState: PaneExpansionState = remember { PaneExpansionState() }, - paneExpansionDragHandle: (@Composable (PaneExpansionState) -> Unit)? = null, - primaryPane: @Composable ThreePaneScaffoldScope.() -> Unit, -) { - val scaffoldState = remember { SeekableTransitionState(scaffoldValue) } - LaunchedEffect(key1 = scaffoldValue) { scaffoldState.animateTo(scaffoldValue) } - ThreePaneScaffold( - modifier = modifier, - scaffoldDirective = scaffoldDirective, - scaffoldState = scaffoldState, - paneOrder = paneOrder, - secondaryPane = secondaryPane, - tertiaryPane = tertiaryPane, - paneExpansionState = paneExpansionState, - paneExpansionDragHandle = paneExpansionDragHandle, - primaryPane = primaryPane - ) -} - -@ExperimentalMaterial3AdaptiveApi -@Composable -internal fun ThreePaneScaffold( - modifier: Modifier, - scaffoldDirective: PaneScaffoldDirective, - scaffoldState: SeekableTransitionState, - paneOrder: ThreePaneScaffoldHorizontalOrder, - secondaryPane: @Composable ThreePaneScaffoldScope.() -> Unit, - tertiaryPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null, - // TODO(conradchen): Moves to use the specific remember function - paneExpansionState: PaneExpansionState = remember { PaneExpansionState() }, - paneExpansionDragHandle: (@Composable (PaneExpansionState) -> Unit)? = null, - primaryPane: @Composable ThreePaneScaffoldScope.() -> Unit, -) { - val layoutDirection = LocalLayoutDirection.current - val ltrPaneOrder = - remember(paneOrder, layoutDirection) { paneOrder.toLtrOrder(layoutDirection) } - val previousScaffoldValue = remember { ThreePaneScaffoldValueHolder(scaffoldState.targetState) } - val spacerSize = - with(LocalDensity.current) { scaffoldDirective.horizontalPartitionSpacerSize.roundToPx() } - val paneMotion = - remember(scaffoldState.targetState, ltrPaneOrder, spacerSize) { - val previousValue = previousScaffoldValue.value - previousScaffoldValue.value = scaffoldState.targetState - calculateThreePaneMotion( - previousScaffoldValue = previousValue, - currentScaffoldValue = scaffoldState.targetState, - paneOrder = ltrPaneOrder, - spacerSize = spacerSize - ) - } - - val currentTransition = rememberTransition(scaffoldState) - - LookaheadScope { - // Create PaneWrappers for each of the panes and map the transitions according to each pane - // role and order. - val contents = - listOf<@Composable () -> Unit>( - { - remember(currentTransition, this@LookaheadScope) { - ThreePaneScaffoldScopeImpl( - ThreePaneScaffoldRole.Primary, - currentTransition, - scaffoldState, - this@LookaheadScope - ) - } - .apply { - positionAnimationSpec = paneMotion.positionAnimationSpec - sizeAnimationSpec = paneMotion.sizeAnimationSpec - enterTransition = - paneMotion.enterTransition( - ThreePaneScaffoldRole.Primary, - ltrPaneOrder - ) - exitTransition = - paneMotion.exitTransition( - ThreePaneScaffoldRole.Primary, - ltrPaneOrder - ) - } - .primaryPane() - }, - { - remember(currentTransition, this@LookaheadScope) { - ThreePaneScaffoldScopeImpl( - ThreePaneScaffoldRole.Secondary, - currentTransition, - scaffoldState, - this@LookaheadScope - ) - } - .apply { - positionAnimationSpec = paneMotion.positionAnimationSpec - sizeAnimationSpec = paneMotion.sizeAnimationSpec - enterTransition = - paneMotion.enterTransition( - ThreePaneScaffoldRole.Secondary, - ltrPaneOrder - ) - exitTransition = - paneMotion.exitTransition( - ThreePaneScaffoldRole.Secondary, - ltrPaneOrder - ) - } - .secondaryPane() - }, - { - if (tertiaryPane != null) { - remember(currentTransition, this@LookaheadScope) { - ThreePaneScaffoldScopeImpl( - ThreePaneScaffoldRole.Tertiary, - currentTransition, - scaffoldState, - this@LookaheadScope - ) - } - .apply { - positionAnimationSpec = paneMotion.positionAnimationSpec - sizeAnimationSpec = paneMotion.sizeAnimationSpec - enterTransition = - paneMotion.enterTransition( - ThreePaneScaffoldRole.Tertiary, - ltrPaneOrder - ) - exitTransition = - paneMotion.exitTransition( - ThreePaneScaffoldRole.Tertiary, - ltrPaneOrder - ) - } - .tertiaryPane() - } - }, - { - if (paneExpansionDragHandle != null) { - paneExpansionDragHandle(paneExpansionState) - } - } - ) - - val measurePolicy = - remember(paneExpansionState) { - ThreePaneContentMeasurePolicy( - scaffoldDirective, - scaffoldState.targetState, - paneExpansionState, - ltrPaneOrder, - ) - } - .apply { - this.scaffoldDirective = scaffoldDirective - this.scaffoldValue = scaffoldState.targetState - this.paneOrder = ltrPaneOrder - } - - Layout(contents = contents, modifier = modifier, measurePolicy = measurePolicy) - } -} - -@OptIn(ExperimentalMaterial3AdaptiveApi::class) -private class ThreePaneScaffoldValueHolder(var value: ThreePaneScaffoldValue) - -@OptIn(ExperimentalMaterial3AdaptiveApi::class) -private class ThreePaneContentMeasurePolicy( - scaffoldDirective: PaneScaffoldDirective, - scaffoldValue: ThreePaneScaffoldValue, - val paneExpansionState: PaneExpansionState, - paneOrder: ThreePaneScaffoldHorizontalOrder, -) : MultiContentMeasurePolicy { - var scaffoldDirective by mutableStateOf(scaffoldDirective) - var scaffoldValue by mutableStateOf(scaffoldValue) - var paneOrder by mutableStateOf(paneOrder) - - /** - * Data class that is used to store the position and width of an expanded pane to be reused when - * the pane is being hidden. - */ - data class PanePlacement(var positionX: Int = 0, var measuredWidth: Int = 0) - - private val placementsCache = - mapOf( - ThreePaneScaffoldRole.Primary to PanePlacement(), - ThreePaneScaffoldRole.Secondary to PanePlacement(), - ThreePaneScaffoldRole.Tertiary to PanePlacement() - ) - - override fun MeasureScope.measure( - measurables: List>, - constraints: Constraints, - ): MeasureResult { - val primaryMeasurables = measurables[0] - val secondaryMeasurables = measurables[1] - val tertiaryMeasurables = measurables[2] - val dragHandleMeasurables = measurables[3] - return layout(constraints.maxWidth, constraints.maxHeight) { - if (coordinates == null) { - return@layout - } - val visiblePanes = - getPanesMeasurables( - paneOrder = paneOrder, - primaryMeasurables = primaryMeasurables, - scaffoldValue = scaffoldValue, - secondaryMeasurables = secondaryMeasurables, - tertiaryMeasurables = tertiaryMeasurables - ) { - it != PaneAdaptedValue.Hidden - } - - val hiddenPanes = - getPanesMeasurables( - paneOrder = paneOrder, - primaryMeasurables = primaryMeasurables, - scaffoldValue = scaffoldValue, - secondaryMeasurables = secondaryMeasurables, - tertiaryMeasurables = tertiaryMeasurables - ) { - it == PaneAdaptedValue.Hidden - } - - val verticalSpacerSize = scaffoldDirective.horizontalPartitionSpacerSize.roundToPx() - val outerBounds = IntRect(0, 0, constraints.maxWidth, constraints.maxHeight) - if (!isLookingAhead) { - paneExpansionState.onMeasured(outerBounds.width, this@measure) - } - - if (!paneExpansionState.isUnspecified() && visiblePanes.size == 2) { - // Pane expansion should override everything - if ( - paneExpansionState.currentDraggingOffset != PaneExpansionState.UnspecifiedWidth - ) { - // Respect the user dragging result if there's any - val halfSpacerSize = verticalSpacerSize / 2 - if (paneExpansionState.currentDraggingOffset <= halfSpacerSize) { - val bounds = - if (paneExpansionState.isDraggingOrSettling) { - outerBounds.copy( - left = - paneExpansionState.currentDraggingOffset * 2 + - outerBounds.left - ) - } else { - outerBounds - } - measureAndPlacePaneWithLocalBounds(bounds, visiblePanes[1], isLookingAhead) - } else if ( - paneExpansionState.currentDraggingOffset >= - outerBounds.width - halfSpacerSize - ) { - val bounds = - if (paneExpansionState.isDraggingOrSettling) { - outerBounds.copy( - right = - paneExpansionState.currentDraggingOffset * 2 - - outerBounds.right - ) - } else { - outerBounds - } - measureAndPlacePaneWithLocalBounds(bounds, visiblePanes[0], isLookingAhead) - } else { - measureAndPlacePaneWithLocalBounds( - outerBounds.copy( - right = paneExpansionState.currentDraggingOffset - halfSpacerSize - ), - visiblePanes[0], - isLookingAhead - ) - measureAndPlacePaneWithLocalBounds( - outerBounds.copy( - left = paneExpansionState.currentDraggingOffset + halfSpacerSize - ), - visiblePanes[1], - isLookingAhead - ) - } - } else { // Pane expansion settings from non-dragging results - val availableWidth = constraints.maxWidth - if ( - paneExpansionState.firstPaneWidth == 0 || - paneExpansionState.firstPanePercentage == 0f - ) { - measureAndPlacePaneWithLocalBounds( - outerBounds, - visiblePanes[1], - isLookingAhead - ) - } else if ( - paneExpansionState.firstPaneWidth >= availableWidth - verticalSpacerSize || - paneExpansionState.firstPanePercentage >= 1f - ) { - measureAndPlacePaneWithLocalBounds( - outerBounds, - visiblePanes[0], - isLookingAhead - ) - } else { - val firstPaneWidth = - if ( - paneExpansionState.firstPaneWidth != - PaneExpansionState.UnspecifiedWidth - ) { - paneExpansionState.firstPaneWidth - } else { - (paneExpansionState.firstPanePercentage * - (availableWidth - verticalSpacerSize)) - .toInt() - } - val firstPaneRight = outerBounds.left + firstPaneWidth - measureAndPlacePaneWithLocalBounds( - outerBounds.copy(right = firstPaneRight), - visiblePanes[0], - isLookingAhead - ) - measureAndPlacePaneWithLocalBounds( - outerBounds.copy(left = firstPaneRight + verticalSpacerSize), - visiblePanes[1], - isLookingAhead - ) - } - } - } else if (scaffoldDirective.excludedBounds.isNotEmpty()) { - val layoutBounds = coordinates!!.boundsInWindow() - val layoutPhysicalPartitions = mutableListOf() - var actualLeft = layoutBounds.left - var actualRight = layoutBounds.right - val actualTop = layoutBounds.top - val actualBottom = layoutBounds.bottom - // Assume hinge bounds are sorted from left to right, non-overlapped. - @Suppress("ListIterator") - scaffoldDirective.excludedBounds.forEach { hingeBound -> - if (hingeBound.left <= actualLeft) { - // The hinge is at the left of the layout, adjust the left edge of - // the current partition to the actual displayable bounds. - actualLeft = max(actualLeft, hingeBound.right) - } else if (hingeBound.right >= actualRight) { - // The hinge is right at the right of the layout and there's no more - // room for more partitions, adjust the right edge of the current - // partition to the actual displayable bounds. - actualRight = min(hingeBound.left, actualRight) - return@forEach - } else { - // The hinge is inside the layout, add the current partition to the list - // and move the left edge of the next partition to the right of the - // hinge. - layoutPhysicalPartitions.add( - Rect(actualLeft, actualTop, hingeBound.left, actualBottom) - ) - actualLeft += max(hingeBound.right, hingeBound.left + verticalSpacerSize) - } - } - if (actualLeft < actualRight) { - // The last partition - layoutPhysicalPartitions.add( - Rect(actualLeft, actualTop, actualRight, actualBottom) - ) - } - if (layoutPhysicalPartitions.size == 0) { - // Display nothing - } else if (layoutPhysicalPartitions.size == 1) { - measureAndPlacePanes( - layoutPhysicalPartitions[0], - verticalSpacerSize, - visiblePanes, - isLookingAhead - ) - } else if (layoutPhysicalPartitions.size < visiblePanes.size) { - // Note that the only possible situation is we have only two physical partitions - // but three expanded panes to show. In this case fit two panes in the larger - // partition. - if (layoutPhysicalPartitions[0].width > layoutPhysicalPartitions[1].width) { - measureAndPlacePanes( - layoutPhysicalPartitions[0], - verticalSpacerSize, - visiblePanes.subList(0, 2), - isLookingAhead - ) - measureAndPlacePane( - layoutPhysicalPartitions[1], - visiblePanes[2], - isLookingAhead - ) - } else { - measureAndPlacePane( - layoutPhysicalPartitions[0], - visiblePanes[0], - isLookingAhead - ) - measureAndPlacePanes( - layoutPhysicalPartitions[1], - verticalSpacerSize, - visiblePanes.subList(1, 3), - isLookingAhead - ) - } - } else { - // Layout each visible pane in a physical partition - visiblePanes.fastForEachIndexed { index, paneMeasurable -> - measureAndPlacePane( - layoutPhysicalPartitions[index], - paneMeasurable, - isLookingAhead - ) - } - } - } else { - measureAndPlacePanesWithLocalBounds( - outerBounds, - verticalSpacerSize, - visiblePanes, - isLookingAhead - ) - } - - if (visiblePanes.size == 2 && dragHandleMeasurables.isNotEmpty()) { - val handleOffsetX = - if ( - !paneExpansionState.isDraggingOrSettling || - paneExpansionState.currentDraggingOffset == - PaneExpansionState.UnspecifiedWidth - ) { - val spacerMiddleOffset = - getSpacerMiddleOffsetX(visiblePanes[0], visiblePanes[1]) - if (!isLookingAhead) { - paneExpansionState.onExpansionOffsetMeasured(spacerMiddleOffset) - } - spacerMiddleOffset - } else { - paneExpansionState.currentDraggingOffset - } - measureAndPlaceDragHandleIfNeeded( - dragHandleMeasurables, - constraints, - outerBounds, - verticalSpacerSize, - handleOffsetX - ) - } else if (!isLookingAhead) { - paneExpansionState.onExpansionOffsetMeasured(PaneExpansionState.UnspecifiedWidth) - } - - // Place the hidden panes to ensure a proper motion at the AnimatedVisibility, - // otherwise the pane will be gone immediately when it's hidden. - // The placement is done using the outerBounds, as the placementsCache holds - // absolute position values. - placeHiddenPanes(outerBounds.top, outerBounds.height, hiddenPanes) - } - } - - @OptIn(ExperimentalMaterial3AdaptiveApi::class) - private fun MeasureScope.getPanesMeasurables( - paneOrder: ThreePaneScaffoldHorizontalOrder, - primaryMeasurables: List, - scaffoldValue: ThreePaneScaffoldValue, - secondaryMeasurables: List, - tertiaryMeasurables: List, - predicate: (PaneAdaptedValue) -> Boolean, - ): List { - return buildList { - paneOrder.forEach { role -> - if (predicate(scaffoldValue[role])) { - when (role) { - ThreePaneScaffoldRole.Primary -> { - createPaneMeasurableIfNeeded( - primaryMeasurables, - ThreePaneScaffoldDefaults.PrimaryPanePriority, - role, - scaffoldDirective.defaultPanePreferredWidth.roundToPx() - ) - } - ThreePaneScaffoldRole.Secondary -> { - createPaneMeasurableIfNeeded( - secondaryMeasurables, - ThreePaneScaffoldDefaults.SecondaryPanePriority, - role, - scaffoldDirective.defaultPanePreferredWidth.roundToPx() - ) - } - ThreePaneScaffoldRole.Tertiary -> { - createPaneMeasurableIfNeeded( - tertiaryMeasurables, - ThreePaneScaffoldDefaults.TertiaryPanePriority, - role, - scaffoldDirective.defaultPanePreferredWidth.roundToPx() - ) - } - } - } - } - } - } - - private fun MutableList.createPaneMeasurableIfNeeded( - measurables: List, - priority: Int, - role: ThreePaneScaffoldRole, - defaultPreferredWidth: Int, - ) { - if (measurables.isNotEmpty()) { - add(PaneMeasurable(measurables[0], priority, role, defaultPreferredWidth)) - } - } - - private fun Placeable.PlacementScope.measureAndPlacePane( - partitionBounds: Rect, - measurable: PaneMeasurable, - isLookingAhead: Boolean, - ) = - measureAndPlacePaneWithLocalBounds( - getLocalBounds(partitionBounds), - measurable, - isLookingAhead - ) - - private fun Placeable.PlacementScope.measureAndPlacePaneWithLocalBounds( - localBounds: IntRect, - measurable: PaneMeasurable, - isLookingAhead: Boolean, - ) { - with(measurable) { - measureAndPlace( - localBounds.width, - localBounds.height, - localBounds.left, - localBounds.top, - if (isLookingAhead) placementsCache else null - ) - } - } - - private fun Placeable.PlacementScope.measureAndPlacePanes( - partitionBounds: Rect, - spacerSize: Int, - measurables: List, - isLookingAhead: Boolean, - ) { - measureAndPlacePanesWithLocalBounds( - getLocalBounds(partitionBounds), - spacerSize, - measurables, - isLookingAhead - ) - } - - private fun Placeable.PlacementScope.measureAndPlacePanesWithLocalBounds( - partitionBounds: IntRect, - spacerSize: Int, - measurables: List, - isLookingAhead: Boolean, - ) { - if (measurables.isEmpty()) { - return - } - val allocatableWidth = partitionBounds.width - (measurables.size - 1) * spacerSize - val totalPreferredWidth = measurables.sumOf { it.measuringWidth } - if (allocatableWidth > totalPreferredWidth) { - // Allocate the remaining space to the pane with the highest priority. - measurables.maxBy { it.priority }.measuringWidth += - allocatableWidth - totalPreferredWidth - } else if (allocatableWidth < totalPreferredWidth) { - // Scale down all panes to fit in the available space. - val scale = allocatableWidth.toFloat() / totalPreferredWidth - measurables.fastForEach { it.measuringWidth = (it.measuringWidth * scale).toInt() } - } - var positionX = partitionBounds.left - measurables.fastForEach { - with(it) { - measureAndPlace( - it.measuringWidth, - partitionBounds.height, - positionX, - partitionBounds.top, - if (isLookingAhead) placementsCache else null - ) - } - positionX += it.measuredWidth + spacerSize - } - } - - @OptIn(ExperimentalMaterial3AdaptiveApi::class) - private fun Placeable.PlacementScope.placeHiddenPanes( - partitionTop: Int, - partitionHeight: Int, - measurables: List, - ) { - // When panes are being hidden, apply each pane's width and position from the cache to - // maintain the those before it's hidden by the AnimatedVisibility. - measurables.fastForEach { - if (!it.isAnimatedPane) { - // When panes are not animated, we don't need to measure and place them. - return - } - val cachedPanePlacement = placementsCache[it.role]!! - with(it) { - measureAndPlace( - cachedPanePlacement.measuredWidth, - partitionHeight, - cachedPanePlacement.positionX, - partitionTop, - null, - ThreePaneScaffoldDefaults.HiddenPaneZIndex - ) - } - } - } - - private fun Placeable.PlacementScope.getLocalBounds(bounds: Rect): IntRect { - return bounds.translate(coordinates!!.windowToLocal(Offset.Zero)).roundToIntRect() - } - - private fun Placeable.PlacementScope.measureAndPlaceDragHandleIfNeeded( - measurables: List, - constraints: Constraints, - contentBounds: IntRect, - maxHandleWidth: Int, - offsetX: Int, - ) { - if (offsetX == PaneExpansionState.UnspecifiedWidth) { - return - } - val placeables = - measurables.fastMap { - it.measure(Constraints(maxWidth = maxHandleWidth, maxHeight = contentBounds.height)) - } - val halfMaxWidth = placeables.fastMaxOfOrNull { it.width }!! / 2 - val clampedOffsetX = - offsetX.coerceIn(contentBounds.left + halfMaxWidth, contentBounds.right - halfMaxWidth) - placeables.fastForEach { - it.place(clampedOffsetX - it.width / 2, (constraints.maxHeight - it.height) / 2) - } - } - - private fun getSpacerMiddleOffsetX(paneLeft: PaneMeasurable, paneRight: PaneMeasurable): Int { - return when { - paneLeft.measuredAndPlaced && paneRight.measuredAndPlaced -> - (paneLeft.placedPositionX + paneLeft.measuredWidth + paneRight.placedPositionX) / 2 - paneLeft.measuredAndPlaced -> paneLeft.placedPositionX + paneLeft.measuredWidth - paneRight.measuredAndPlaced -> 0 - else -> PaneExpansionState.UnspecifiedWidth - } - } -} - -private class PaneMeasurable( - val measurable: Measurable, - val priority: Int, - val role: ThreePaneScaffoldRole, - defaultPreferredWidth: Int, -) { - private val data = - ((measurable.parentData as? PaneScaffoldParentData) ?: PaneScaffoldParentData()) - - var measuringWidth = - if (data.preferredWidth == null || data.preferredWidth!!.isNaN()) { - defaultPreferredWidth - } else { - data.preferredWidth!!.toInt() - } - - val isAnimatedPane = data.isAnimatedPane - - var measuredWidth = 0 - private set - - var measuredHeight = 0 - private set - - var placedPositionX = 0 - private set - - var placedPositionY = 0 - private set - - var measuredAndPlaced = false - private set - - fun Placeable.PlacementScope.measureAndPlace( - width: Int, - height: Int, - positionX: Int, - positionY: Int, - placementsCache: Map?, - zIndex: Float = 0f, - ) { - measuredWidth = width - measuredHeight = height - placedPositionX = positionX - placedPositionY = positionY - measurable.measure(Constraints.fixed(width, height)).place(positionX, positionY, zIndex) - measuredAndPlaced = true - - // Cache the values to be used when this measurable's role is being hidden. - // See placeHiddenPanes. - if (placementsCache != null) { - val cachedPanePlacement = placementsCache[role]!! - cachedPanePlacement.measuredWidth = width - cachedPanePlacement.positionX = positionX - } - } -} - -/** Scope for the panes of [ThreePaneScaffold]. */ -@ExperimentalMaterial3AdaptiveApi -sealed interface ThreePaneScaffoldScope : PaneScaffoldScope, LookaheadScope { - /** The [ThreePaneScaffoldRole] of the current pane in the scope. */ - val role: ThreePaneScaffoldRole - - /** The current scaffold state transition between [ThreePaneScaffoldValue]s. */ - val scaffoldStateTransition: Transition - - /** The current fraction of the scaffold state transition. */ - val scaffoldStateTransitionFraction: Float - - /** - * The position animation spec of the associated pane to the scope. [AnimatedPane] will use this - * value to perform pane animations during scaffold state changes. - */ - val positionAnimationSpec: FiniteAnimationSpec - - /** - * The size animation spec of the associated pane to the scope. [AnimatedPane] will use this - * value to perform pane animations during scaffold state changes. - */ - val sizeAnimationSpec: FiniteAnimationSpec - - /** - * The [EnterTransition] of the associated pane. [AnimatedPane] will use this value to perform - * pane entering animations when it's showing during scaffold state changes. - */ - val enterTransition: EnterTransition - - /** - * The [ExitTransition] of the associated pane. [AnimatedPane] will use this value to perform - * pane exiting animations when it's hiding during scaffold state changes. - */ - val exitTransition: ExitTransition -} - -@OptIn(ExperimentalMaterial3AdaptiveApi::class) -private class ThreePaneScaffoldScopeImpl( - override val role: ThreePaneScaffoldRole, - override val scaffoldStateTransition: Transition, - private val transitionState: SeekableTransitionState, - lookaheadScope: LookaheadScope, -) : ThreePaneScaffoldScope, LookaheadScope by lookaheadScope, PaneScaffoldScopeImpl() { - override val scaffoldStateTransitionFraction: Float - get() = - if (transitionState.currentState == transitionState.targetState) { - 1f - } else { - transitionState.fraction - } - - override var positionAnimationSpec: FiniteAnimationSpec by mutableStateOf(snap()) - override var sizeAnimationSpec: FiniteAnimationSpec by mutableStateOf(snap()) - override var enterTransition by mutableStateOf(EnterTransition.None) - override var exitTransition by mutableStateOf(ExitTransition.None) -} - -/** - * Provides default values of [ThreePaneScaffold] and the calculation functions of - * [ThreePaneScaffoldValue]. - */ -@ExperimentalMaterial3AdaptiveApi -internal object ThreePaneScaffoldDefaults { - // TODO(conradchen): consider declaring a value class for priority - const val PrimaryPanePriority = 10 - const val SecondaryPanePriority = 5 - const val TertiaryPanePriority = 1 - - /** - * Creates a default [ThreePaneScaffoldAdaptStrategies]. - * - * @param primaryPaneAdaptStrategy the adapt strategy of the primary pane - * @param secondaryPaneAdaptStrategy the adapt strategy of the secondary pane - * @param tertiaryPaneAdaptStrategy the adapt strategy of the tertiary pane - */ - fun adaptStrategies( - primaryPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide, - secondaryPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide, - tertiaryPaneAdaptStrategy: AdaptStrategy = AdaptStrategy.Hide, - ): ThreePaneScaffoldAdaptStrategies = - ThreePaneScaffoldAdaptStrategies( - primaryPaneAdaptStrategy, - secondaryPaneAdaptStrategy, - tertiaryPaneAdaptStrategy - ) - - /** - * The negative z-index of hidden panes to make visible panes always show upon hidden panes - * during pane animations. - */ - const val HiddenPaneZIndex = -0.1f -} diff --git a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldAdaptStrategies.kt b/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldAdaptStrategies.kt deleted file mode 100644 index 99793ed..0000000 --- a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldAdaptStrategies.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2023 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.compose.material3.adaptive.layout - -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi - -/** - * The adaptation specs of [ThreePaneScaffold]. This class denotes how each pane of - * [ThreePaneScaffold] should be adapted. It should be used as an input parameter of - * [calculateThreePaneScaffoldValue] to decide the [ThreePaneScaffoldValue]. - * - * @param primaryPaneAdaptStrategy [AdaptStrategy] of the primary pane of [ThreePaneScaffold] - * @param secondaryPaneAdaptStrategy [AdaptStrategy] of the secondary pane of [ThreePaneScaffold] - * @param tertiaryPaneAdaptStrategy [AdaptStrategy] of the tertiary pane of [ThreePaneScaffold] - * @constructor create an instance of [ThreePaneScaffoldAdaptStrategies] - */ -@ExperimentalMaterial3AdaptiveApi -class ThreePaneScaffoldAdaptStrategies( - private val primaryPaneAdaptStrategy: AdaptStrategy, - private val secondaryPaneAdaptStrategy: AdaptStrategy, - private val tertiaryPaneAdaptStrategy: AdaptStrategy, -) { - operator fun get(role: ThreePaneScaffoldRole): AdaptStrategy { - return when (role) { - ThreePaneScaffoldRole.Primary -> primaryPaneAdaptStrategy - ThreePaneScaffoldRole.Secondary -> secondaryPaneAdaptStrategy - ThreePaneScaffoldRole.Tertiary -> tertiaryPaneAdaptStrategy - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is ThreePaneScaffoldAdaptStrategies) return false - if (primaryPaneAdaptStrategy != other.primaryPaneAdaptStrategy) return false - if (secondaryPaneAdaptStrategy != other.secondaryPaneAdaptStrategy) return false - if (tertiaryPaneAdaptStrategy != other.tertiaryPaneAdaptStrategy) return false - return true - } - - override fun hashCode(): Int { - var result = primaryPaneAdaptStrategy.hashCode() - result = 31 * result + secondaryPaneAdaptStrategy.hashCode() - result = 31 * result + tertiaryPaneAdaptStrategy.hashCode() - return result - } -} diff --git a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldDestinationItem.kt b/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldDestinationItem.kt deleted file mode 100644 index 3c0b995..0000000 --- a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldDestinationItem.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.compose.material3.adaptive.layout - -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi - -/** - * An item representing a navigation destination in a [ThreePaneScaffold]. - * - * @param pane the pane destination of the navigation. - * @param content the optional content, or an id representing the content of the destination. The - * type [T] must be storable in a Bundle. - */ -@ExperimentalMaterial3AdaptiveApi -class ThreePaneScaffoldDestinationItem( - val pane: ThreePaneScaffoldRole, - val content: T? = null, -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is ThreePaneScaffoldDestinationItem<*>) return false - - if (pane != other.pane) return false - if (content != other.content) return false - - return true - } - - override fun hashCode(): Int { - var result = pane.hashCode() - result = 31 * result + (content?.hashCode() ?: 0) - return result - } - - override fun toString(): String { - return "ThreePaneScaffoldDestinationItem(pane=$pane, content=$content)" - } -} diff --git a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldHorizontalOrder.kt b/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldHorizontalOrder.kt deleted file mode 100644 index 62f7be0..0000000 --- a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldHorizontalOrder.kt +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2023 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.compose.material3.adaptive.layout - -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi -import androidx.compose.runtime.Immutable -import androidx.compose.ui.unit.LayoutDirection - -/** - * Represents the horizontal order of panes in a [ThreePaneScaffold] from start to end. Note that - * the values of [firstPane], [secondPane] and [thirdPane] have to be different, otherwise - * [IllegalArgumentException] will be thrown. - * - * @param firstPane The first pane from the start of the [ThreePaneScaffold] - * @param secondPane The second pane from the start of the [ThreePaneScaffold] - * @param thirdPane The third pane from the start of the [ThreePaneScaffold] - * @constructor create an instance of [ThreePaneScaffoldHorizontalOrder] - */ -@ExperimentalMaterial3AdaptiveApi -@Immutable -internal class ThreePaneScaffoldHorizontalOrder( - val firstPane: ThreePaneScaffoldRole, - val secondPane: ThreePaneScaffoldRole, - val thirdPane: ThreePaneScaffoldRole, -) : PaneScaffoldHorizontalOrder { - init { - require(firstPane != secondPane && secondPane != thirdPane && firstPane != thirdPane) { - "invalid ThreePaneScaffoldHorizontalOrder($firstPane, $secondPane, $thirdPane)" + - " - panes must be unique" - } - } - - override val size = 3 - - override fun indexOf(role: ThreePaneScaffoldRole) = - when (role) { - firstPane -> 0 - secondPane -> 1 - thirdPane -> 2 - else -> -1 - } - - override fun forEach(action: (ThreePaneScaffoldRole) -> Unit) { - action(firstPane) - action(secondPane) - action(thirdPane) - } - - override fun forEachIndexed(action: (Int, ThreePaneScaffoldRole) -> Unit) { - action(0, firstPane) - action(1, secondPane) - action(2, thirdPane) - } - - override fun forEachIndexedReversed(action: (Int, ThreePaneScaffoldRole) -> Unit) { - action(2, thirdPane) - action(1, secondPane) - action(0, firstPane) - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is ThreePaneScaffoldHorizontalOrder) return false - if (firstPane != other.firstPane) return false - if (secondPane != other.secondPane) return false - if (thirdPane != other.thirdPane) return false - return true - } - - override fun hashCode(): Int { - var result = firstPane.hashCode() - result = 31 * result + secondPane.hashCode() - result = 31 * result + thirdPane.hashCode() - return result - } -} - -/** Converts a bidirectional order to a left-to-right order. */ -@ExperimentalMaterial3AdaptiveApi -internal fun ThreePaneScaffoldHorizontalOrder.toLtrOrder( - layoutDirection: LayoutDirection, -): ThreePaneScaffoldHorizontalOrder { - return if (layoutDirection == LayoutDirection.Rtl) { - ThreePaneScaffoldHorizontalOrder(thirdPane, secondPane, firstPane) - } else { - this - } -} - -/** The set of the available pane roles of [ThreePaneScaffold]. */ -enum class ThreePaneScaffoldRole { - /** - * The primary pane of [ThreePaneScaffold]. It is supposed to have the highest priority during - * layout adaptation and usually contains the most important content of the screen, like content - * details in a list-detail settings. - */ - Primary, - - /** - * The secondary pane of [ThreePaneScaffold]. It is supposed to have the second highest priority - * during layout adaptation and usually contains the supplement content of the screen, like - * content list in a list-detail settings. - */ - Secondary, - - /** - * The tertiary pane of [ThreePaneScaffold]. It is supposed to have the lowest priority during - * layout adaptation and usually contains the additional info which will only be shown under - * user interaction. - */ - Tertiary -} diff --git a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldValue.kt b/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldValue.kt deleted file mode 100644 index 5e46e2e..0000000 --- a/wwm/androidx/compose/material3/adaptive/layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldValue.kt +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright 2023 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.compose.material3.adaptive.layout - -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi -import androidx.compose.runtime.Immutable -import androidx.compose.ui.util.fastForEachReversed - -@ExperimentalMaterial3AdaptiveApi -private inline fun buildThreePaneScaffoldValue( - buildAction: (ThreePaneScaffoldRole) -> PaneAdaptedValue, -): ThreePaneScaffoldValue { - return ThreePaneScaffoldValue( - buildAction(ThreePaneScaffoldRole.Primary), - buildAction(ThreePaneScaffoldRole.Secondary), - buildAction(ThreePaneScaffoldRole.Tertiary) - ) -} - -/** - * Calculates the current adapted value of [ThreePaneScaffold] according to the given - * [maxHorizontalPartitions], [adaptStrategies] and [currentDestination]. The returned value can be - * used as a unique representation of the current layout structure. - * - * The function will treat the current destination as the highest priority and then adapt the rest - * panes according to the order of [ThreePaneScaffoldRole.Primary], - * [ThreePaneScaffoldRole.Secondary] and [ThreePaneScaffoldRole.Tertiary]. If there are still - * remaining partitions to put the pane, the pane will be set as [PaneAdaptedValue.Expanded], - * otherwise it will be adapted according to its associated [AdaptStrategy]. - * - * @param maxHorizontalPartitions The maximum allowed partitions along the horizontal axis, i.e., - * how many expanded panes can be shown at the same time. - * @param adaptStrategies The adapt strategies of each pane role that [ThreePaneScaffold] supports, - * the default value will be [ThreePaneScaffoldDefaults.threePaneScaffoldAdaptStrategies]. - * @param currentDestination The current destination item, which will be treated as having the - * highest priority, can be `null`. - */ -@ExperimentalMaterial3AdaptiveApi -fun calculateThreePaneScaffoldValue( - maxHorizontalPartitions: Int, - adaptStrategies: ThreePaneScaffoldAdaptStrategies, - currentDestination: ThreePaneScaffoldDestinationItem<*>?, -): ThreePaneScaffoldValue { - var expandedCount = if (currentDestination != null) 1 else 0 - return buildThreePaneScaffoldValue { role -> - when { - role == currentDestination?.pane -> PaneAdaptedValue.Expanded - expandedCount < maxHorizontalPartitions -> { - expandedCount++ - PaneAdaptedValue.Expanded - } - else -> adaptStrategies[role].adapt() - } - } -} - -/** - * Calculates the current adapted value of [ThreePaneScaffold] according to the given - * [maxHorizontalPartitions], [adaptStrategies] and [destinationHistory]. The returned value can be - * used as a unique representation of the current layout structure. - * - * The function will treat the current focus as the highest priority and then adapt the rest panes - * according to the order of [ThreePaneScaffoldRole.Primary], [ThreePaneScaffoldRole.Secondary] and - * [ThreePaneScaffoldRole.Tertiary]. If there are still remaining partitions to put the pane, the - * pane will be set as [PaneAdaptedValue.Expanded], otherwise it will be adapted according to its - * associated [AdaptStrategy]. - * - * @param maxHorizontalPartitions The maximum allowed partitions along the horizontal axis, i.e., - * how many expanded panes can be shown at the same time. - * @param adaptStrategies The adapt strategies of each pane role that [ThreePaneScaffold] supports, - * the default value will be [ThreePaneScaffoldDefaults.threePaneScaffoldAdaptStrategies]. - * @param destinationHistory The history of past destination items. The last destination will have - * the highest priority, and the second last destination will have the second highest priority, - * and so forth until all panes have a priority assigned. Note that the last destination is - * supposed to be the last item of the provided list. - */ -@ExperimentalMaterial3AdaptiveApi -fun calculateThreePaneScaffoldValue( - maxHorizontalPartitions: Int, - adaptStrategies: ThreePaneScaffoldAdaptStrategies, - destinationHistory: List>, -): ThreePaneScaffoldValue { - var expandedCount = 0 - var primaryPaneAdaptedValue: PaneAdaptedValue? = null - var secondaryPaneAdaptedValue: PaneAdaptedValue? = null - var tertiaryPaneAdaptedValue: PaneAdaptedValue? = null - destinationHistory.fastForEachReversed { - if (expandedCount >= maxHorizontalPartitions) { - return@fastForEachReversed - } - when (it.pane) { - ThreePaneScaffoldRole.Primary -> { - if (primaryPaneAdaptedValue == null) { - primaryPaneAdaptedValue = PaneAdaptedValue.Expanded - expandedCount++ - } - } - ThreePaneScaffoldRole.Secondary -> { - if (secondaryPaneAdaptedValue == null) { - secondaryPaneAdaptedValue = PaneAdaptedValue.Expanded - expandedCount++ - } - } - ThreePaneScaffoldRole.Tertiary -> { - if (tertiaryPaneAdaptedValue == null) { - tertiaryPaneAdaptedValue = PaneAdaptedValue.Expanded - expandedCount++ - } - } - } - } - return ThreePaneScaffoldValue( - primary = - primaryPaneAdaptedValue - ?: if (expandedCount < maxHorizontalPartitions) { - expandedCount++ - PaneAdaptedValue.Expanded - } else { - adaptStrategies[ThreePaneScaffoldRole.Primary].adapt() - }, - secondary = - secondaryPaneAdaptedValue - ?: if (expandedCount < maxHorizontalPartitions) { - expandedCount++ - PaneAdaptedValue.Expanded - } else { - adaptStrategies[ThreePaneScaffoldRole.Secondary].adapt() - }, - tertiary = - tertiaryPaneAdaptedValue - ?: if (expandedCount < maxHorizontalPartitions) { - expandedCount++ - PaneAdaptedValue.Expanded - } else { - adaptStrategies[ThreePaneScaffoldRole.Tertiary].adapt() - } - ) -} - -/** - * The adapted value of [ThreePaneScaffold]. It contains each pane's adapted value. - * [ThreePaneScaffold] will use the adapted values to decide which panes should be displayed and how - * they should be displayed. With other input parameters of [ThreePaneScaffold] fixed, each possible - * instance of this class should represent a unique state of [ThreePaneScaffold] and developers can - * compare two [ThreePaneScaffoldValue] to decide if there is a layout structure change. - * - * For a Material-opinionated layout, it's suggested to use [calculateThreePaneScaffoldValue] to - * calculate the current scaffold value. - * - * @param primary [PaneAdaptedValue] of the primary pane of [ThreePaneScaffold] - * @param secondary [PaneAdaptedValue] of the secondary pane of [ThreePaneScaffold] - * @param tertiary [PaneAdaptedValue] of the tertiary pane of [ThreePaneScaffold] - * @constructor create an instance of [ThreePaneScaffoldValue] - */ -@ExperimentalMaterial3AdaptiveApi -@Immutable -class ThreePaneScaffoldValue( - val primary: PaneAdaptedValue, - val secondary: PaneAdaptedValue, - val tertiary: PaneAdaptedValue, -) : PaneScaffoldValue { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is ThreePaneScaffoldValue) return false - if (primary != other.primary) return false - if (secondary != other.secondary) return false - if (tertiary != other.tertiary) return false - return true - } - - override fun hashCode(): Int { - var result = primary.hashCode() - result = 31 * result + secondary.hashCode() - result = 31 * result + tertiary.hashCode() - return result - } - - override fun toString(): String { - return "ThreePaneScaffoldValue(primary=$primary, " + - "secondary=$secondary, " + - "tertiary=$tertiary)" - } - - override operator fun get(role: ThreePaneScaffoldRole): PaneAdaptedValue = - when (role) { - ThreePaneScaffoldRole.Primary -> primary - ThreePaneScaffoldRole.Secondary -> secondary - ThreePaneScaffoldRole.Tertiary -> tertiary - } -} - -@OptIn(ExperimentalMaterial3AdaptiveApi::class) -internal val ThreePaneScaffoldValue.expandedCount: Int - get() { - var count = 0 - if (primary == PaneAdaptedValue.Expanded) { - count++ - } - if (secondary == PaneAdaptedValue.Expanded) { - count++ - } - if (tertiary == PaneAdaptedValue.Expanded) { - count++ - } - return count - } diff --git a/wwm/androidx/compose/material3/adaptive/layout/src/skikoMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.skiko.kt b/wwm/androidx/compose/material3/adaptive/layout/src/skikoMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.skiko.kt deleted file mode 100644 index d4a754b..0000000 --- a/wwm/androidx/compose/material3/adaptive/layout/src/skikoMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.skiko.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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.compose.material3.adaptive.layout - -import androidx.compose.ui.Modifier - -internal actual fun Modifier.systemGestureExclusion(): Modifier = this diff --git a/wwm/androidx/compose/material3/adaptive/navigation/.gitignore b/wwm/androidx/compose/material3/adaptive/navigation/.gitignore deleted file mode 100644 index 796b96d..0000000 --- a/wwm/androidx/compose/material3/adaptive/navigation/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/wwm/androidx/compose/material3/adaptive/navigation/build.gradle.kts b/wwm/androidx/compose/material3/adaptive/navigation/build.gradle.kts deleted file mode 100644 index 102305d..0000000 --- a/wwm/androidx/compose/material3/adaptive/navigation/build.gradle.kts +++ /dev/null @@ -1,17 +0,0 @@ -plugins { - id("wwm.compose.multiplatform") -} - -kotlin { - sourceSets { - commonMain { - dependencies { - implementation(project(":wwm-androidx-compose-material3-adaptive")) - implementation(project(":wwm-androidx-compose-material3-adaptive-layout")) - } - dependencies { - implementation(compose.ui) - } - } - } -} diff --git a/wwm/androidx/compose/material3/adaptive/navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/AndroidThreePaneScaffold.android.kt b/wwm/androidx/compose/material3/adaptive/navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/AndroidThreePaneScaffold.android.kt deleted file mode 100644 index bcede8c..0000000 --- a/wwm/androidx/compose/material3/adaptive/navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/AndroidThreePaneScaffold.android.kt +++ /dev/null @@ -1,108 +0,0 @@ -/* - * 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.compose.material3.adaptive.navigation - -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.SupportingPaneScaffold as BaseSupportingPaneScaffold -import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier - -/** - * A version of [androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold] that supports - * navigation and back handling out of the box, controlled by [ThreePaneScaffoldNavigator]. - * - * @param navigator The navigator instance to navigate through the scaffold. - * @param listPane the list pane of the scaffold, which is supposed to hold a list of item summaries - * that can be selected from, for example, the inbox mail list of a mail app. See - * [ListDetailPaneScaffoldRole.List]. - * @param detailPane the detail pane of the scaffold, which is supposed to hold the detailed info of - * a selected item, for example, the mail content currently being viewed. See - * [ListDetailPaneScaffoldRole.Detail]. - * @param modifier [Modifier] of the scaffold layout. - * @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 defaultBackBehavior the default back navigation behavior when the system back event - * happens. See [BackNavigationBehavior] for the use cases of each behavior. - */ -@ExperimentalMaterial3AdaptiveApi -@Composable -fun NavigableListDetailPaneScaffold( - navigator: ThreePaneScaffoldNavigator, - listPane: @Composable ThreePaneScaffoldScope.() -> Unit, - detailPane: @Composable ThreePaneScaffoldScope.() -> Unit, - modifier: Modifier = Modifier, - extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null, - defaultBackBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilContentChange, -) { - // TODO(b/330584029): support predictive back - BackHandler(enabled = navigator.canNavigateBack(defaultBackBehavior)) { - navigator.navigateBack(defaultBackBehavior) - } - BaseListDetailPaneScaffold( - modifier = modifier, - directive = navigator.scaffoldDirective, - value = navigator.scaffoldValue, - detailPane = detailPane, - listPane = listPane, - extraPane = extraPane - ) -} - -/** - * A version of [androidx.compose.material3.adaptive.layout.SupportingPaneScaffold] that supports - * navigation and back handling out of the box, controlled by [ThreePaneScaffoldNavigator]. - * - * @param navigator The navigator instance to navigate through the scaffold. - * @param mainPane the main pane of the scaffold, which is supposed to hold the major content of an - * app, for example, the editing screen of a doc app. See [SupportingPaneScaffoldRole.Main]. - * @param supportingPane the supporting pane of the scaffold, which is supposed to hold the support - * content of an app, for example, the comment list of a doc app. See - * [SupportingPaneScaffoldRole.Supporting]. - * @param modifier [Modifier] of the scaffold layout. - * @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 defaultBackBehavior the default back navigation behavior when the system back event - * happens. See [BackNavigationBehavior] for the use cases of each behavior. - */ -@ExperimentalMaterial3AdaptiveApi -@Composable -fun NavigableSupportingPaneScaffold( - navigator: ThreePaneScaffoldNavigator, - mainPane: @Composable ThreePaneScaffoldScope.() -> Unit, - supportingPane: @Composable ThreePaneScaffoldScope.() -> Unit, - modifier: Modifier = Modifier, - extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null, - defaultBackBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilContentChange, -) { - // TODO(b/330584029): support predictive back - BackHandler(enabled = navigator.canNavigateBack(defaultBackBehavior)) { - navigator.navigateBack(defaultBackBehavior) - } - BaseSupportingPaneScaffold( - modifier = modifier, - directive = navigator.scaffoldDirective, - value = navigator.scaffoldValue, - mainPane = mainPane, - supportingPane = supportingPane, - extraPane = extraPane - ) -} diff --git a/wwm/androidx/compose/material3/adaptive/navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/BackNavigationBehavior.kt b/wwm/androidx/compose/material3/adaptive/navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/BackNavigationBehavior.kt deleted file mode 100644 index 07c722a..0000000 --- a/wwm/androidx/compose/material3/adaptive/navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/BackNavigationBehavior.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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.compose.material3.adaptive.navigation - -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi -import androidx.compose.material3.adaptive.layout.PaneAdaptedValue -import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem -import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole -import kotlin.jvm.JvmInline - -/** A class to control how back navigation should behave in a [ThreePaneScaffoldNavigator]. */ -@ExperimentalMaterial3AdaptiveApi -@JvmInline -value class BackNavigationBehavior private constructor(private val description: String) { - override fun toString(): String = this.description - - companion object { - /** Pop the latest destination from the backstack. */ - val PopLatest = BackNavigationBehavior("PopLatest") - - /** - * Pop destinations from the backstack until there is a change in the scaffold value. - * - * For example, in a single-pane layout, this will skip entries until the current - * destination is a different [ThreePaneScaffoldRole]. In a multi-pane layout, this will - * skip entries until the [PaneAdaptedValue] of any pane changes. - */ - val PopUntilScaffoldValueChange = BackNavigationBehavior("PopUntilScaffoldValueChange") - - /** - * Pop destinations from the backstack until there is a change in the current destination - * pane. - * - * In a single-pane layout, this should behave similarly to [PopUntilScaffoldValueChange]. - * In a multi-pane layout, it is possible for both the current destination and previous - * destination to be showing at the same time, so this may not result in a visual change in - * the scaffold. - */ - val PopUntilCurrentDestinationChange = - BackNavigationBehavior("PopUntilCurrentDestinationChange") - - /** - * Pop destinations from the backstack until there is a content change. - * - * A "content change" is defined as either a change in the content of the current - * [ThreePaneScaffoldDestinationItem], or a change in the scaffold value (similar to - * [PopUntilScaffoldValueChange]). - */ - val PopUntilContentChange = BackNavigationBehavior("PopUntilContentChange") - } -} diff --git a/wwm/androidx/compose/material3/adaptive/navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldNavigator.kt b/wwm/androidx/compose/material3/adaptive/navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldNavigator.kt deleted file mode 100644 index b976beb..0000000 --- a/wwm/androidx/compose/material3/adaptive/navigation/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation/ThreePaneScaffoldNavigator.kt +++ /dev/null @@ -1,460 +0,0 @@ -/* - * Copyright 2023 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.compose.material3.adaptive.navigation - -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi -import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo -import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold -import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldDefaults -import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole -import androidx.compose.material3.adaptive.layout.PaneScaffoldDirective -import androidx.compose.material3.adaptive.layout.SupportingPaneScaffold -import androidx.compose.material3.adaptive.layout.SupportingPaneScaffoldDefaults -import androidx.compose.material3.adaptive.layout.SupportingPaneScaffoldRole -import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldAdaptStrategies -import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem -import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole -import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue -import androidx.compose.material3.adaptive.layout.calculatePaneScaffoldDirective -import androidx.compose.material3.adaptive.layout.calculateThreePaneScaffoldValue -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.Saver -import androidx.compose.runtime.saveable.listSaver -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.util.fastMap - -/** - * The common interface of the default navigation implementations for different three-pane - * scaffolds. - * - * In general, we suggest you to use [rememberListDetailPaneScaffoldNavigator] or - * [rememberSupportingPaneScaffoldNavigator] to get remembered default instances of this interface - * for [ListDetailPaneScaffold] and [SupportingPaneScaffold], respectively. Those default - * implementations work independently from any navigation frameworks. - * - * If you need to integrate with existing navigation frameworks or implement your own custom - * navigation logic, usually creating whole new APIs that's tailored for your own solution will be - * recommended, instead of implementing this interface. But we recommend you refer to the API design - * and the default implementation to get better understanding and address the intricacies of - * navigation in an adaptive scenario. - * - * @param T the type representing the content, or id of the content, for a navigation destination. - * This type must be storable in a Bundle. Used to customize navigation behavior (for example, - * [BackNavigationBehavior]). If this customization is unneeded, you can pass [Nothing]. - */ -@ExperimentalMaterial3AdaptiveApi -@Stable -interface ThreePaneScaffoldNavigator { - /** - * The current layout directives that the associated three pane scaffold needs to follow. It's - * supposed to be automatically updated when the window configuration changes. - */ - val scaffoldDirective: PaneScaffoldDirective - - /** - * The current layout value of the associated three pane scaffold value, which represents unique - * layout states of the scaffold. - */ - val scaffoldValue: ThreePaneScaffoldValue - - /** - * The current destination as tracked by the navigator. - * - * Implementors of this interface should ensure this value is updated whenever a navigation - * operation is performed. - */ - val currentDestination: ThreePaneScaffoldDestinationItem? - - /** - * Indicates if the navigator should be aware of pane destination history when deciding the - * result [ThreePaneScaffoldValue] by a navigation operation. If the value is `false`, only the - * current destination will be considered in the scaffold value calculation. - * - * @see calculateThreePaneScaffoldValue for more detailed explanation about history awareness. - */ - var isDestinationHistoryAware: Boolean - - /** - * Navigates to a new destination. The new destination is supposed to have the highest priority - * when calculating the new [scaffoldValue]. - * - * Implementors of this interface should ensure the new destination pane will be expanded or - * adapted in a reasonable way so it provides users the sense that the new destination is the - * pane currently being used. - * - * @param pane the new destination pane. - * @param content the optional content, or an id representing the content of the new - * destination. - */ - fun navigateTo(pane: ThreePaneScaffoldRole, content: T? = null) - - /** - * Returns `true` if there is a previous destination to navigate back to. - * - * Implementors of this interface should ensure the logic of this function is consistent with - * [navigateBack]. - * - * @param backNavigationBehavior the behavior describing which backstack entries may be skipped - * during the back navigation. See [BackNavigationBehavior]. - */ - fun canNavigateBack( - backNavigationBehavior: BackNavigationBehavior = - BackNavigationBehavior.PopUntilScaffoldValueChange, - ): Boolean - - /** - * Navigates to the previous destination. Returns `true` if there is a previous destination to - * navigate back to. - * - * Implementors of this interface should ensure the logic of this function is consistent with - * [canNavigateBack]. - * - * @param backNavigationBehavior the behavior describing which backstack entries may be skipped - * during the back navigation. See [BackNavigationBehavior]. - */ - fun navigateBack( - backNavigationBehavior: BackNavigationBehavior = - BackNavigationBehavior.PopUntilScaffoldValueChange, - ): Boolean -} - -/** - * Returns a remembered default implementation of [ThreePaneScaffoldNavigator] for - * [ListDetailPaneScaffold], which will be updated automatically when the input values change. The - * default navigator is supposed to be used independently from any navigation frameworks and handles - * the navigation purely inside the [ListDetailPaneScaffold]. - * - * @param T the type representing the content, or id of the content, for a navigation destination. - * This type must be storable in a Bundle. Used to customize navigation behavior (for example, - * [BackNavigationBehavior]). If this customization is unneeded, you can pass [Nothing]. - * @param scaffoldDirective the current layout directives to follow. The default value will be - * calculated with [calculatePaneScaffoldDirective] using - * [WindowAdaptiveInfo][androidx.compose.material3.adaptive.WindowAdaptiveInfo] retrieved from the - * current context. - * @param adaptStrategies adaptation strategies of each pane. - * @param isDestinationHistoryAware `true` if the scaffold value calculation should be aware of the - * full destination history, instead of just the current destination. See - * [calculateThreePaneScaffoldValue] for more relevant details. - * @param initialDestinationHistory the initial pane destination history of the scaffold, by default - * it will be just the list pane. - */ -@ExperimentalMaterial3AdaptiveApi -@Composable -fun rememberListDetailPaneScaffoldNavigator( - scaffoldDirective: PaneScaffoldDirective = - calculatePaneScaffoldDirective(currentWindowAdaptiveInfo()), - adaptStrategies: ThreePaneScaffoldAdaptStrategies = - ListDetailPaneScaffoldDefaults.adaptStrategies(), - isDestinationHistoryAware: Boolean = true, - initialDestinationHistory: List> = - DefaultListDetailPaneHistory, -): ThreePaneScaffoldNavigator = - rememberThreePaneScaffoldNavigator( - scaffoldDirective, - adaptStrategies, - isDestinationHistoryAware, - initialDestinationHistory - ) - -/** - * Returns a remembered default implementation of [ThreePaneScaffoldNavigator] for - * [ListDetailPaneScaffold], which will be updated automatically when the input values change. The - * default navigator is supposed to be used independently from any navigation frameworks and handles - * the navigation purely inside the [ListDetailPaneScaffold]. - * - * @param scaffoldDirective the current layout directives to follow. The default value will be - * calculated with [calculatePaneScaffoldDirective] using - * [WindowAdaptiveInfo][androidx.compose.material3.adaptive.WindowAdaptiveInfo] retrieved from the - * current context. - * @param adaptStrategies adaptation strategies of each pane. - * @param isDestinationHistoryAware `true` if the scaffold value calculation should be aware of the - * full destination history, instead of just the current destination. See - * [calculateThreePaneScaffoldValue] for more relevant details. - */ -@ExperimentalMaterial3AdaptiveApi -@Composable -fun rememberListDetailPaneScaffoldNavigator( - scaffoldDirective: PaneScaffoldDirective = - calculatePaneScaffoldDirective(currentWindowAdaptiveInfo()), - adaptStrategies: ThreePaneScaffoldAdaptStrategies = - ListDetailPaneScaffoldDefaults.adaptStrategies(), - isDestinationHistoryAware: Boolean = true, -): ThreePaneScaffoldNavigator = - rememberListDetailPaneScaffoldNavigator( - scaffoldDirective, - adaptStrategies, - isDestinationHistoryAware, - ) - -/** - * Returns a remembered default implementation of [ThreePaneScaffoldNavigator] for - * [SupportingPaneScaffold], which will be updated automatically when the input values change. The - * default navigator is supposed to be used independently from any navigation frameworks and handles - * the navigation purely inside the [SupportingPaneScaffold]. - * - * @param T the type representing the content, or id of the content, for a navigation destination. - * This type must be storable in a Bundle. Used to customize navigation behavior (for example, - * [BackNavigationBehavior]). If this customization is unneeded, you can pass [Nothing]. - * @param scaffoldDirective the current layout directives to follow. The default value will be - * calculated with [calculatePaneScaffoldDirective] using - * [WindowAdaptiveInfo][androidx.compose.material3.adaptive.WindowAdaptiveInfo] retrieved from the - * current context. - * @param adaptStrategies adaptation strategies of each pane. - * @param isDestinationHistoryAware `true` if the scaffold value calculation should be aware of the - * full destination history, instead of just the current destination. See - * [calculateThreePaneScaffoldValue] for more relevant details. - * @param initialDestinationHistory the initial destination history of the scaffold, by default it - * will be just the main pane. - */ -@ExperimentalMaterial3AdaptiveApi -@Composable -fun rememberSupportingPaneScaffoldNavigator( - scaffoldDirective: PaneScaffoldDirective = - calculatePaneScaffoldDirective(currentWindowAdaptiveInfo()), - adaptStrategies: ThreePaneScaffoldAdaptStrategies = - SupportingPaneScaffoldDefaults.adaptStrategies(), - isDestinationHistoryAware: Boolean = true, - initialDestinationHistory: List> = - DefaultSupportingPaneHistory, -): ThreePaneScaffoldNavigator = - rememberThreePaneScaffoldNavigator( - scaffoldDirective, - adaptStrategies, - isDestinationHistoryAware, - initialDestinationHistory - ) - -/** - * Returns a remembered default implementation of [ThreePaneScaffoldNavigator] for - * [SupportingPaneScaffold], which will be updated automatically when the input values change. The - * default navigator is supposed to be used independently from any navigation frameworks and handles - * the navigation purely inside the [SupportingPaneScaffold]. - * - * @param scaffoldDirective the current layout directives to follow. The default value will be - * calculated with [calculatePaneScaffoldDirective] using - * [WindowAdaptiveInfo][androidx.compose.material3.adaptive.WindowAdaptiveInfo] retrieved from the - * current context. - * @param adaptStrategies adaptation strategies of each pane. - * @param isDestinationHistoryAware `true` if the scaffold value calculation should be aware of the - * full destination history, instead of just the current destination. See - * [calculateThreePaneScaffoldValue] for more relevant details. - */ -@ExperimentalMaterial3AdaptiveApi -@Composable -fun rememberSupportingPaneScaffoldNavigator( - scaffoldDirective: PaneScaffoldDirective = - calculatePaneScaffoldDirective(currentWindowAdaptiveInfo()), - adaptStrategies: ThreePaneScaffoldAdaptStrategies = - SupportingPaneScaffoldDefaults.adaptStrategies(), - isDestinationHistoryAware: Boolean = true, -): ThreePaneScaffoldNavigator = - rememberSupportingPaneScaffoldNavigator( - scaffoldDirective, - adaptStrategies, - isDestinationHistoryAware, - ) - -@ExperimentalMaterial3AdaptiveApi -@Composable -internal fun rememberThreePaneScaffoldNavigator( - scaffoldDirective: PaneScaffoldDirective, - adaptStrategies: ThreePaneScaffoldAdaptStrategies, - isDestinationHistoryAware: Boolean, - initialDestinationHistory: List>, -): ThreePaneScaffoldNavigator = - rememberSaveable( - saver = - DefaultThreePaneScaffoldNavigator.saver( - scaffoldDirective, - adaptStrategies, - isDestinationHistoryAware - ) - ) { - DefaultThreePaneScaffoldNavigator( - initialDestinationHistory = initialDestinationHistory, - initialScaffoldDirective = scaffoldDirective, - initialAdaptStrategies = adaptStrategies, - initialIsDestinationHistoryAware = isDestinationHistoryAware - ) - } - .apply { - this.scaffoldDirective = scaffoldDirective - this.adaptStrategies = adaptStrategies - this.isDestinationHistoryAware = isDestinationHistoryAware - } - -@OptIn(ExperimentalMaterial3AdaptiveApi::class) -internal class DefaultThreePaneScaffoldNavigator( - initialDestinationHistory: List>, - initialScaffoldDirective: PaneScaffoldDirective, - initialAdaptStrategies: ThreePaneScaffoldAdaptStrategies, - initialIsDestinationHistoryAware: Boolean, -) : ThreePaneScaffoldNavigator { - - private val destinationHistory = - mutableStateListOf>().apply { - addAll(initialDestinationHistory) - } - - override var scaffoldDirective by mutableStateOf(initialScaffoldDirective) - - override var isDestinationHistoryAware by mutableStateOf(initialIsDestinationHistoryAware) - - var adaptStrategies by mutableStateOf(initialAdaptStrategies) - - override val currentDestination - get() = destinationHistory.lastOrNull() - - override val scaffoldValue by derivedStateOf { - calculateScaffoldValue(destinationHistory.lastIndex) - } - - override fun navigateTo(pane: ThreePaneScaffoldRole, content: T?) { - destinationHistory.add(ThreePaneScaffoldDestinationItem(pane, content)) - } - - override fun canNavigateBack(backNavigationBehavior: BackNavigationBehavior): Boolean = - getPreviousDestinationIndex(backNavigationBehavior) >= 0 - - override fun navigateBack(backNavigationBehavior: BackNavigationBehavior): Boolean { - val previousDestinationIndex = getPreviousDestinationIndex(backNavigationBehavior) - if (previousDestinationIndex < 0) { - destinationHistory.clear() - return false - } - val targetSize = previousDestinationIndex + 1 - while (destinationHistory.size > targetSize) { - destinationHistory.removeLast() - } - return true - } - - private fun getPreviousDestinationIndex(backNavBehavior: BackNavigationBehavior): Int { - if (destinationHistory.size <= 1) { - // No previous destination - return -1 - } - when (backNavBehavior) { - BackNavigationBehavior.PopLatest -> return destinationHistory.lastIndex - 1 - BackNavigationBehavior.PopUntilScaffoldValueChange -> - for (previousDestinationIndex in destinationHistory.lastIndex - 1 downTo 0) { - val previousValue = calculateScaffoldValue(previousDestinationIndex) - if (previousValue != scaffoldValue) { - return previousDestinationIndex - } - } - BackNavigationBehavior.PopUntilCurrentDestinationChange -> - for (previousDestinationIndex in destinationHistory.lastIndex - 1 downTo 0) { - val destination = destinationHistory[previousDestinationIndex].pane - if (destination != currentDestination?.pane) { - return previousDestinationIndex - } - } - BackNavigationBehavior.PopUntilContentChange -> - for (previousDestinationIndex in destinationHistory.lastIndex - 1 downTo 0) { - val content = destinationHistory[previousDestinationIndex].content - if (content != currentDestination?.content) { - return previousDestinationIndex - } - // A scaffold value change also counts as a content change. - val previousValue = calculateScaffoldValue(previousDestinationIndex) - if (previousValue != scaffoldValue) { - return previousDestinationIndex - } - } - } - - return -1 - } - - private fun calculateScaffoldValue(destinationIndex: Int) = - if (destinationIndex == -1) { - calculateThreePaneScaffoldValue( - scaffoldDirective.maxHorizontalPartitions, - adaptStrategies, - null - ) - } else if (isDestinationHistoryAware) { - calculateThreePaneScaffoldValue( - scaffoldDirective.maxHorizontalPartitions, - adaptStrategies, - destinationHistory.subList(0, destinationIndex + 1) - ) - } else { - calculateThreePaneScaffoldValue( - scaffoldDirective.maxHorizontalPartitions, - adaptStrategies, - destinationHistory[destinationIndex] - ) - } - - companion object { - /** To keep destination history saved */ - fun saver( - initialScaffoldDirective: PaneScaffoldDirective, - initialAdaptStrategies: ThreePaneScaffoldAdaptStrategies, - initialDestinationHistoryAware: Boolean, - ): Saver, *> { - val destinationItemSaver = destinationItemSaver() - return listSaver( - save = { - it.destinationHistory.fastMap { destination -> - with(destinationItemSaver) { save(destination) } - } - }, - restore = { - DefaultThreePaneScaffoldNavigator( - initialDestinationHistory = - it.fastMap { savedDestination -> - destinationItemSaver.restore(savedDestination!!)!! - }, - initialScaffoldDirective = initialScaffoldDirective, - initialAdaptStrategies = initialAdaptStrategies, - initialIsDestinationHistoryAware = initialDestinationHistoryAware - ) - } - ) - } - } -} - -@OptIn(ExperimentalMaterial3AdaptiveApi::class) -internal fun destinationItemSaver(): Saver, Any> = - listSaver( - save = { listOf(it.pane, it.content) }, - restore = { - @Suppress("UNCHECKED_CAST") - (ThreePaneScaffoldDestinationItem( - pane = it[0] as ThreePaneScaffoldRole, - content = it[1] as T? - )) - } - ) - -@OptIn(ExperimentalMaterial3AdaptiveApi::class) -private val DefaultListDetailPaneHistory: List> = - listOf(ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List)) - -@OptIn(ExperimentalMaterial3AdaptiveApi::class) -private val DefaultSupportingPaneHistory: List> = - listOf(ThreePaneScaffoldDestinationItem(SupportingPaneScaffoldRole.Main)) diff --git a/wwm/androidx/compose/material3/adaptive/navigation/suite/build.gradle.kts b/wwm/androidx/compose/material3/adaptive/navigation/suite/build.gradle.kts index c7906bf..45414b0 100644 --- a/wwm/androidx/compose/material3/adaptive/navigation/suite/build.gradle.kts +++ b/wwm/androidx/compose/material3/adaptive/navigation/suite/build.gradle.kts @@ -6,8 +6,8 @@ kotlin { sourceSets { commonMain { dependencies { - implementation(project(":wwm-androidx-compose-material3-adaptive")) - implementation(project(":wwm-androidx-window-core")) + implementation(jetbrains.androidx.window.core) + implementation(jetbrains.compose.material3.adaptive) } dependencies { implementation(compose.material3) diff --git a/wwm/androidx/compose/material3/adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidPosture.android.kt b/wwm/androidx/compose/material3/adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidPosture.android.kt deleted file mode 100644 index 1c0315e..0000000 --- a/wwm/androidx/compose/material3/adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidPosture.android.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2023 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.compose.material3.adaptive - -import androidx.compose.ui.graphics.toComposeRect -import androidx.window.layout.FoldingFeature - -/** - * Calculates the [Posture] for a given list of [FoldingFeature]s. This methods converts framework - * folding info into the Material-opinionated posture info. - */ -@ExperimentalMaterial3AdaptiveApi -fun calculatePosture(foldingFeatures: List): Posture { - var isTableTop = false - val hingeList = mutableListOf() - @Suppress("ListIterator") - foldingFeatures.forEach { - if ( - it.orientation == FoldingFeature.Orientation.HORIZONTAL && - it.state == FoldingFeature.State.HALF_OPENED - ) { - isTableTop = true - } - hingeList.add( - HingeInfo( - bounds = it.bounds.toComposeRect(), - isFlat = it.state == FoldingFeature.State.FLAT, - isVertical = it.orientation == FoldingFeature.Orientation.VERTICAL, - isSeparating = it.isSeparating, - isOccluding = it.occlusionType == FoldingFeature.OcclusionType.FULL - ) - ) - } - return Posture(isTableTop, hingeList) -} diff --git a/wwm/androidx/compose/material3/adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidWindowAdaptiveInfo.android.kt b/wwm/androidx/compose/material3/adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidWindowAdaptiveInfo.android.kt deleted file mode 100644 index 5e59a48..0000000 --- a/wwm/androidx/compose/material3/adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/AndroidWindowAdaptiveInfo.android.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2023 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.compose.material3.adaptive - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.IntSize -import androidx.compose.ui.unit.toSize -import androidx.window.core.layout.WindowSizeClass -import androidx.window.layout.FoldingFeature -import androidx.window.layout.WindowInfoTracker -import androidx.window.layout.WindowMetricsCalculator -import kotlinx.coroutines.flow.map - -@OptIn(ExperimentalMaterial3AdaptiveApi::class) -@Composable -actual fun currentWindowAdaptiveInfo(): WindowAdaptiveInfo { - val windowSize = with(LocalDensity.current) { currentWindowSize().toSize().toDpSize() } - return WindowAdaptiveInfo( - WindowSizeClass.compute(windowSize.width.value, windowSize.height.value), - calculatePosture(collectFoldingFeaturesAsState().value) - ) -} - -/** - * Returns and automatically update the current window size from [WindowMetricsCalculator]. - * - * @return an [IntSize] that represents the current window size. - */ -@Composable -fun currentWindowSize(): IntSize { - // Observe view configuration changes and recalculate the size class on each change. We can't - // use Activity#onConfigurationChanged as this will sometimes fail to be called on different - // API levels, hence why this function needs to be @Composable so we can observe the - // ComposeView's configuration changes. - LocalConfiguration.current - val windowBounds = - WindowMetricsCalculator.getOrCreate() - .computeCurrentWindowMetrics(LocalContext.current) - .bounds - return IntSize(windowBounds.width(), windowBounds.height()) -} - -/** - * Collects the current window folding features from [WindowInfoTracker] in to a [State]. - * - * @return a [State] of a [FoldingFeature] list. - */ -@Composable -fun collectFoldingFeaturesAsState(): State> { - val context = LocalContext.current - return remember(context) { - WindowInfoTracker.getOrCreate(context).windowLayoutInfo(context).map { - it.displayFeatures.filterIsInstance() - } - } - .collectAsState(emptyList()) -} diff --git a/wwm/androidx/compose/material3/adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ExperimentalMaterial3AdaptiveApi.kt b/wwm/androidx/compose/material3/adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ExperimentalMaterial3AdaptiveApi.kt deleted file mode 100644 index 3ad72c5..0000000 --- a/wwm/androidx/compose/material3/adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ExperimentalMaterial3AdaptiveApi.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2023 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.compose.material3.adaptive - -@RequiresOptIn( - "This material3 adaptive API is experimental and is likely to change or to be" + - "removed in the future." -) -@Retention(AnnotationRetention.BINARY) -annotation class ExperimentalMaterial3AdaptiveApi diff --git a/wwm/androidx/compose/material3/adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/Posture.kt b/wwm/androidx/compose/material3/adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/Posture.kt deleted file mode 100644 index be6d442..0000000 --- a/wwm/androidx/compose/material3/adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/Posture.kt +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2023 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.compose.material3.adaptive - -import androidx.compose.runtime.Immutable -import androidx.compose.ui.geometry.Rect - -/** - * Posture info that can help make layout adaptation decisions. For example when - * [Posture.separatingVerticalHingeBounds] is not empty, the layout may want to avoid putting any - * content over those hinge area. We suggest to use [calculatePosture] to retrieve instances of this - * class in applications, unless you have a strong need of customization that cannot be fulfilled by - * the default implementation. - * - * Note that the hinge bounds will be represent as [Rect] with window coordinates, instead of layout - * coordinate. - * - * @constructor create an instance of [Posture] - * @property isTabletop `true` if the current window is considered as in the table top mode, i.e. - * there is one half-opened horizontal hinge in the middle of the current window. When this is - * `true` it usually means it's hard for users to interact with the window area around the hinge - * and developers may consider separating the layout along the hinge and show software keyboard or - * other controls in the bottom half of the window. - * @property hingeList a list of all hinges that are relevant to the posture. - */ -@Immutable -class Posture(val isTabletop: Boolean = false, val hingeList: List = emptyList()) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is Posture) return false - if (isTabletop != other.isTabletop) return false - if (hingeList != other.hingeList) return false - return true - } - - override fun hashCode(): Int { - var result = isTabletop.hashCode() - result = 31 * result + hingeList.hashCode() - return result - } - - override fun toString(): String { - @Suppress("ListIterator") - return "Posture(isTabletop=$isTabletop, " + "hinges=[${hingeList.joinToString(", ")}])" - } -} - -/** Returns the list of vertical hinge bounds that are separating. */ -val Posture.separatingVerticalHingeBounds - get() = hingeList.getBounds { isVertical && isSeparating } - -/** Returns the list of vertical hinge bounds that are occluding. */ -val Posture.occludingVerticalHingeBounds - get() = hingeList.getBounds { isVertical && isOccluding } - -/** Returns the list of all vertical hinge bounds. */ -val Posture.allVerticalHingeBounds - get() = hingeList.getBounds { isVertical } - -/** Returns the list of horizontal hinge bounds that are separating. */ -val Posture.separatingHorizontalHingeBounds - get() = hingeList.getBounds { !isVertical && isSeparating } - -/** Returns the list of horizontal hinge bounds that are occluding. */ -val Posture.occludingHorizontalHingeBounds - get() = hingeList.getBounds { !isVertical && isOccluding } - -/** Returns the list of all horizontal hinge bounds. */ -val Posture.allHorizontalHingeBounds - get() = hingeList.getBounds { !isVertical } - -/** - * A class that contains the info of a hinge relevant to a [Posture]. - * - * @param bounds the bounds of the hinge in the relevant viewport. - * @param isFlat `true` if the hinge is fully open and the relevant window space presented to the - * user is flat. - * @param isVertical `true` if the hinge is a vertical one, i.e., it separates the viewport into - * left and right; `false` if the hinge is horizontal, i.e., it separates the viewport into top - * and bottom. - * @param isSeparating `true` if the hinge creates two logical display areas. - * @param isOccluding `true` if the hinge conceals part of the display. - */ -@Immutable -class HingeInfo( - val bounds: Rect, - val isFlat: Boolean, - val isVertical: Boolean, - val isSeparating: Boolean, - val isOccluding: Boolean, -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is HingeInfo) return false - if (bounds != other.bounds) return false - if (isFlat != other.isFlat) return false - if (isVertical != other.isVertical) return false - if (isSeparating != other.isSeparating) return false - if (isOccluding != other.isOccluding) return false - return true - } - - override fun hashCode(): Int { - var result = bounds.hashCode() - result = 31 * result + isFlat.hashCode() - result = 31 * result + isVertical.hashCode() - result = 31 * result + isSeparating.hashCode() - result = 31 * result + isOccluding.hashCode() - return result - } - - override fun toString(): String { - return "HingeInfo(bounds=$bounds, " + - "isFlat=$isFlat, " + - "isVertical=$isVertical, " + - "isSeparating=$isSeparating, " + - "isOccluding=$isOccluding)" - } -} - -private inline fun List.getBounds(predicate: HingeInfo.() -> Boolean): List = - @Suppress("ListIterator") mapNotNull { if (it.predicate()) it.bounds else null } diff --git a/wwm/androidx/compose/material3/adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/WindowAdaptiveInfo.kt b/wwm/androidx/compose/material3/adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/WindowAdaptiveInfo.kt deleted file mode 100644 index eada34c..0000000 --- a/wwm/androidx/compose/material3/adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/WindowAdaptiveInfo.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2023 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.compose.material3.adaptive - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.IntSize -import androidx.compose.ui.unit.toSize -import androidx.window.core.layout.WindowSizeClass - -/** - * Calculates and returns [WindowAdaptiveInfo] of the provided context. It's a convenient function - * that uses the default [WindowSizeClass] constructor and the default [Posture] calculation - * functions to retrieve [WindowSizeClass] and [Posture]. - * - * @return [WindowAdaptiveInfo] of the provided context - */ -@Composable -fun currentWindowAdaptiveInfo(): WindowAdaptiveInfo { - val windowSize = with(LocalDensity.current) { - currentWindowSize().toSize().toDpSize() - } - return WindowAdaptiveInfo( - windowSizeClass = WindowSizeClass.compute(windowSize.width.value, windowSize.height.value), - windowPosture = calculatePosture(), - ) -} - -/** - * This class collects window info that affects adaptation decisions. An adaptive layout is supposed - * to use the info from this class to decide how the layout is supposed to be adapted. - * - * @param windowSizeClass [WindowSizeClass] of the current window. - * @param windowPosture [Posture] of the current window. - * @constructor create an instance of [WindowAdaptiveInfo] - */ -@Immutable -class WindowAdaptiveInfo(val windowSizeClass: WindowSizeClass, val windowPosture: Posture) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is WindowAdaptiveInfo) return false - if (windowSizeClass != other.windowSizeClass) return false - if (windowPosture != other.windowPosture) return false - return true - } - - override fun hashCode(): Int { - var result = windowSizeClass.hashCode() - result = 31 * result + windowPosture.hashCode() - return result - } - - override fun toString(): String { - return "WindowAdaptiveInfo(windowSizeClass=$windowSizeClass, windowPosture=$windowPosture)" - } -} - -@Composable -internal expect fun currentWindowSize(): IntSize - -@Composable -internal expect fun calculatePosture(): Posture diff --git a/wwm/androidx/compose/material3/adaptive/src/skikoMain/kotlin/androidx/compose/material3/adaptive/WindowAdaptiveInfo.skiko.kt b/wwm/androidx/compose/material3/adaptive/src/skikoMain/kotlin/androidx/compose/material3/adaptive/WindowAdaptiveInfo.skiko.kt deleted file mode 100644 index 4f505d8..0000000 --- a/wwm/androidx/compose/material3/adaptive/src/skikoMain/kotlin/androidx/compose/material3/adaptive/WindowAdaptiveInfo.skiko.kt +++ /dev/null @@ -1,14 +0,0 @@ -package androidx.compose.material3.adaptive - -import androidx.compose.runtime.Composable -import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.platform.LocalWindowInfo -import androidx.compose.ui.unit.IntSize - -@OptIn(ExperimentalComposeUiApi::class) -@Composable -internal actual fun currentWindowSize(): IntSize = LocalWindowInfo.current.containerSize - -@OptIn(ExperimentalMaterial3AdaptiveApi::class) -@Composable -internal actual fun calculatePosture(): Posture = Posture() diff --git a/wwm/androidx/window/core/.gitignore b/wwm/androidx/window/core/.gitignore deleted file mode 100644 index 796b96d..0000000 --- a/wwm/androidx/window/core/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/wwm/androidx/window/core/build.gradle.kts b/wwm/androidx/window/core/build.gradle.kts deleted file mode 100644 index d465d48..0000000 --- a/wwm/androidx/window/core/build.gradle.kts +++ /dev/null @@ -1,3 +0,0 @@ -plugins { - id("wwm.kotlin.multiplatform") -} diff --git a/wwm/androidx/window/core/src/commonMain/kotlin/androidx/window/core/layout/WindowHeightSizeClass.kt b/wwm/androidx/window/core/src/commonMain/kotlin/androidx/window/core/layout/WindowHeightSizeClass.kt deleted file mode 100644 index ecfd35d..0000000 --- a/wwm/androidx/window/core/src/commonMain/kotlin/androidx/window/core/layout/WindowHeightSizeClass.kt +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2022 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.window.core.layout - -import androidx.window.core.layout.WindowHeightSizeClass.Companion.COMPACT -import androidx.window.core.layout.WindowHeightSizeClass.Companion.EXPANDED -import androidx.window.core.layout.WindowHeightSizeClass.Companion.MEDIUM -import kotlin.jvm.JvmField - -/** - * A class to represent the height size buckets for a viewport. The possible values are [COMPACT], - * [MEDIUM], and [EXPANDED]. [WindowHeightSizeClass] should not be used as a proxy for the device - * type. It is possible to have resizeable windows in different device types. - * The viewport might change from a [COMPACT] all the way to an [EXPANDED] size class. - */ -class WindowHeightSizeClass private constructor( - private val rawValue: Int, -) { - - override fun toString(): String { - val name = when (this) { - COMPACT -> "COMPACT" - MEDIUM -> "MEDIUM" - EXPANDED -> "EXPANDED" - else -> "UNKNOWN" - } - return "WindowHeightSizeClass: $name" - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null) return false - if (this::class != other::class) return false - - val that = other as WindowHeightSizeClass - - return rawValue == that.rawValue - } - - override fun hashCode(): Int { - return rawValue - } - - companion object { - /** - * A bucket to represent a compact height, typical for a phone that is in landscape. - */ - @JvmField - val COMPACT: WindowHeightSizeClass = WindowHeightSizeClass(0) - - /** - * A bucket to represent a medium height, typical for a phone in portrait or a tablet. - */ - @JvmField - val MEDIUM: WindowHeightSizeClass = WindowHeightSizeClass(1) - - /** - * A bucket to represent an expanded height window, typical for a large tablet or a - * desktop form-factor. - */ - @JvmField - val EXPANDED: WindowHeightSizeClass = WindowHeightSizeClass(2) - - /** - * Returns a recommended [WindowHeightSizeClass] for the height of a window given the - * height in DP. - * @param dpHeight the height of the window in DP - * @return A recommended size class for the height - * @throws IllegalArgumentException if the height is negative - */ - internal fun compute(dpHeight: Float): WindowHeightSizeClass { - require(dpHeight >= 0) { "Height must be positive, received $dpHeight" } - return when { - dpHeight < 480 -> COMPACT - dpHeight < 900 -> MEDIUM - else -> EXPANDED - } - } - } -} diff --git a/wwm/androidx/window/core/src/commonMain/kotlin/androidx/window/core/layout/WindowSizeClass.kt b/wwm/androidx/window/core/src/commonMain/kotlin/androidx/window/core/layout/WindowSizeClass.kt deleted file mode 100644 index 6d29b61..0000000 --- a/wwm/androidx/window/core/src/commonMain/kotlin/androidx/window/core/layout/WindowSizeClass.kt +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2022 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.window.core.layout - -import kotlin.jvm.JvmStatic - -/** - * [WindowSizeClass] provides breakpoints for a viewport. Designers should design around the - * different combinations of width and height buckets. Developers should use the different buckets - * to specify the layouts. Ideally apps will work well in each bucket and by extension work well - * across multiple devices. If two devices are in similar buckets they should behave similarly. - * - * This class is meant to be a common definition that can be shared across different device types. - * Application developers can use WindowSizeClass to have standard window buckets and design the UI - * around those buckets. Library developers can use these buckets to create different UI with - * respect to each bucket. This will help with consistency across multiple device types. - * - * A library developer use-case can be creating some navigation UI library. For a size - * class with the [WindowWidthSizeClass.EXPANDED] width it might be more reasonable to have a side - * navigation. For a [WindowWidthSizeClass.COMPACT] width, a bottom navigation might be a better - * fit. - * - * An application use-case can be applied for apps that use a list-detail pattern. The app can use - * the [WindowWidthSizeClass.MEDIUM] to determine if there is enough space to show the list and the - * detail side by side. If all apps follow this guidance then it will present a very consistent user - * experience. - * - * @see WindowWidthSizeClass - * @see WindowHeightSizeClass - */ -class WindowSizeClass private constructor( - val windowWidthSizeClass: WindowWidthSizeClass, - val windowHeightSizeClass: WindowHeightSizeClass, -) { - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null) return false - if (this::class != other::class) return false - - val that = other as WindowSizeClass - - if (windowWidthSizeClass != that.windowWidthSizeClass) return false - if (windowHeightSizeClass != that.windowHeightSizeClass) return false - - return true - } - - override fun hashCode(): Int { - var result = windowWidthSizeClass.hashCode() - result = 31 * result + windowHeightSizeClass.hashCode() - return result - } - - override fun toString(): String { - return "SizeClass { widthSizeClass: $windowWidthSizeClass," + - " heightSizeClass: $windowHeightSizeClass }" - } - - companion object { - /** - * Computes the [WindowSizeClass] for the given width and height in DP. - * @param dpWidth width of a window in DP. - * @param dpHeight height of a window in DP. - * @return [WindowSizeClass] that is recommended for the given dimensions. - * @throws IllegalArgumentException if [dpWidth] or [dpHeight] is - * negative. - */ - @JvmStatic - fun compute(dpWidth: Float, dpHeight: Float): WindowSizeClass { - val windowWidthSizeClass = WindowWidthSizeClass.compute(dpWidth) - val windowHeightSizeClass = WindowHeightSizeClass.compute(dpHeight) - return WindowSizeClass(windowWidthSizeClass, windowHeightSizeClass) - } - } -} diff --git a/wwm/androidx/window/core/src/commonMain/kotlin/androidx/window/core/layout/WindowWidthSizeClass.kt b/wwm/androidx/window/core/src/commonMain/kotlin/androidx/window/core/layout/WindowWidthSizeClass.kt deleted file mode 100644 index ccbab6b..0000000 --- a/wwm/androidx/window/core/src/commonMain/kotlin/androidx/window/core/layout/WindowWidthSizeClass.kt +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2022 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.window.core.layout - -import androidx.window.core.layout.WindowWidthSizeClass.Companion.COMPACT -import androidx.window.core.layout.WindowWidthSizeClass.Companion.EXPANDED -import androidx.window.core.layout.WindowWidthSizeClass.Companion.MEDIUM -import kotlin.jvm.JvmField - -/** - * A class to represent the width size buckets for a viewport. The possible values are [COMPACT], - * [MEDIUM], and [EXPANDED]. [WindowWidthSizeClass] should not be used as a proxy for the device - * type. It is possible to have resizeable windows in different device types. - * The viewport might change from a [COMPACT] all the way to an [EXPANDED] size class. - */ -class WindowWidthSizeClass private constructor( - private val rawValue: Int, -) { - override fun toString(): String { - val name = when (this) { - COMPACT -> "COMPACT" - MEDIUM -> "MEDIUM" - EXPANDED -> "EXPANDED" - else -> "UNKNOWN" - } - return "WindowWidthSizeClass: $name" - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null) return false - if (this::class != other::class) return false - - val that = other as WindowWidthSizeClass - - return rawValue == that.rawValue - } - - override fun hashCode(): Int { - return rawValue - } - - companion object { - /** - * A bucket to represent a compact width window, typical for a phone in portrait. - */ - @JvmField - val COMPACT: WindowWidthSizeClass = WindowWidthSizeClass(0) - - /** - * A bucket to represent a medium width window, typical for a phone in landscape or - * a tablet. - */ - @JvmField - val MEDIUM: WindowWidthSizeClass = WindowWidthSizeClass(1) - - /** - * A bucket to represent an expanded width window, typical for a large tablet or desktop - * form-factor. - */ - @JvmField - val EXPANDED: WindowWidthSizeClass = WindowWidthSizeClass(2) - - /** - * Returns a recommended [WindowWidthSizeClass] for the width of a window given the width - * in DP. - * @param dpWidth the width of the window in DP - * @return A recommended size class for the width - * @throws IllegalArgumentException if the width is negative - */ - internal fun compute(dpWidth: Float): WindowWidthSizeClass { - require(dpWidth >= 0) { "Width must be positive, received $dpWidth" } - return when { - dpWidth < 600 -> COMPACT - dpWidth < 840 -> MEDIUM - else -> EXPANDED - } - } - } -} diff --git a/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/component/AchievementListUi.kt b/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/component/AchievementListUi.kt index 602fc78..9ae5923 100644 --- a/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/component/AchievementListUi.kt +++ b/wwm/core/feature/achievements/src/commonMain/kotlin/dev/omico/wwm/feature/achievements/component/AchievementListUi.kt @@ -3,7 +3,6 @@ */ package dev.omico.wwm.feature.achievements.component -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding @@ -77,7 +76,6 @@ internal fun AchievementListPaneUi( ) } -@OptIn(ExperimentalFoundationApi::class) private fun LazyListScope.achievementsCategoryItem( state: AchievementsUiState, category: WwAchievementCategory, diff --git a/wwm/core/ui/foundation/build.gradle.kts b/wwm/core/ui/foundation/build.gradle.kts index 0ce7fcf..bef5d71 100644 --- a/wwm/core/ui/foundation/build.gradle.kts +++ b/wwm/core/ui/foundation/build.gradle.kts @@ -6,11 +6,7 @@ kotlin { sourceSets { commonMain { dependencies { - api(project(":wwm-androidx-compose-material3-adaptive")) - api(project(":wwm-androidx-compose-material3-adaptive-layout")) - api(project(":wwm-androidx-compose-material3-adaptive-navigation")) api(project(":wwm-androidx-compose-material3-adaptive-navigation-suite")) - api(project(":wwm-androidx-window-core")) api(project(":wwm-core-foundation")) } dependencies { @@ -18,6 +14,10 @@ kotlin { api(compose.components.resources) api(compose.material3) api(delusion.compose) + api(jetbrains.androidx.window.core) + api(jetbrains.compose.material3.adaptive) + api(jetbrains.compose.material3.adaptive.layout) + api(jetbrains.compose.material3.adaptive.navigation) } } diff --git a/wwm/core/ui/src/commonMain/kotlin/dev/omico/wwm/ui/DialogWithIconButton.kt b/wwm/core/ui/src/commonMain/kotlin/dev/omico/wwm/ui/DialogWithIconButton.kt index ce3f668..89184a2 100644 --- a/wwm/core/ui/src/commonMain/kotlin/dev/omico/wwm/ui/DialogWithIconButton.kt +++ b/wwm/core/ui/src/commonMain/kotlin/dev/omico/wwm/ui/DialogWithIconButton.kt @@ -4,7 +4,6 @@ package dev.omico.wwm.ui import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -21,7 +20,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.painter.Painter -@OptIn(ExperimentalAnimationApi::class) @Composable fun DialogWithIconButton( painter: Painter, diff --git a/wwm/core/ui/src/commonMain/kotlin/dev/omico/wwm/ui/NavigationSuiteType.kt b/wwm/core/ui/src/commonMain/kotlin/dev/omico/wwm/ui/NavigationSuiteType.kt index 82f6334..9148b0e 100644 --- a/wwm/core/ui/src/commonMain/kotlin/dev/omico/wwm/ui/NavigationSuiteType.kt +++ b/wwm/core/ui/src/commonMain/kotlin/dev/omico/wwm/ui/NavigationSuiteType.kt @@ -3,11 +3,9 @@ */ package dev.omico.wwm.ui -import androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteType import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.staticCompositionLocalOf -@OptIn(ExperimentalMaterial3AdaptiveNavigationSuiteApi::class) val LocalNavigationSuiteType: ProvidableCompositionLocal = staticCompositionLocalOf { error("No NavigationSuiteType provided") }