You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
142 lines
5.6 KiB
142 lines
5.6 KiB
package org.jetbrains.compose.splitpane |
|
|
|
import androidx.compose.foundation.layout.Box |
|
import androidx.compose.runtime.Composable |
|
import androidx.compose.ui.Modifier |
|
import androidx.compose.ui.layout.Layout |
|
import androidx.compose.ui.layout.Placeable |
|
import androidx.compose.ui.unit.Constraints |
|
import kotlin.math.roundToInt |
|
|
|
private fun Constraints.maxByDirection(isHorizontal: Boolean): Int = if (isHorizontal) maxWidth else maxHeight |
|
private fun Placeable.valueByDirection(isHorizontal: Boolean): Int = if (isHorizontal) width else height |
|
private fun Constraints.withUnconstrainedWidth() = copy(minWidth = 0, maxWidth = Constraints.Infinity) |
|
private fun Constraints.withUnconstrainedHeight() = copy(minHeight = 0, maxHeight = Constraints.Infinity) |
|
|
|
|
|
@OptIn(ExperimentalSplitPaneApi::class) |
|
@Composable |
|
internal actual fun SplitPane( |
|
modifier: Modifier, |
|
isHorizontal: Boolean, |
|
splitPaneState: SplitPaneState, |
|
minimalSizesConfiguration: MinimalSizes, |
|
first: (@Composable () -> Unit)?, |
|
second: (@Composable () -> Unit)?, |
|
splitter: Splitter |
|
) { |
|
if (first == null || second == null) { |
|
first?.let { Box(modifier) { it() } } |
|
second?.let { Box(modifier) { it() } } |
|
return |
|
} |
|
|
|
Layout( |
|
{ |
|
Box { |
|
first() |
|
} |
|
Box { |
|
splitter.measuredPart() |
|
} |
|
Box { |
|
second() |
|
} |
|
Box { |
|
splitter.handlePart() |
|
} |
|
}, |
|
modifier, |
|
) { measurables, constraints -> |
|
with(minimalSizesConfiguration) { |
|
val firstMinSizePx = firstPlaceableMinimalSize.value * density |
|
val secondMinSizePx = secondPlaceableMinimalSize.value * density |
|
|
|
with(splitPaneState) { |
|
val firstMeasurable = measurables[0] |
|
val splitterMeasurable = measurables[1] |
|
val secondMeasurable = measurables[2] |
|
val handleMeasurable = measurables[3] |
|
|
|
// Need the size of the splitter to determine the min/max position |
|
// Constrain the splitter only on the "other" axis |
|
val splitterConstraints = |
|
if (isHorizontal) |
|
constraints.withUnconstrainedWidth() |
|
else |
|
constraints.withUnconstrainedHeight() |
|
val splitterPlaceable = splitterMeasurable.measure(splitterConstraints) |
|
val splitterSize = splitterPlaceable.valueByDirection(isHorizontal) |
|
|
|
@Suppress("UnnecessaryVariable") |
|
val constrainedMin = firstMinSizePx |
|
val maxConstraintOnMainAxis = constraints.maxByDirection(isHorizontal) |
|
val constrainedMax = (maxConstraintOnMainAxis - secondMinSizePx - splitterSize) |
|
.coerceAtLeast(constrainedMin) |
|
|
|
minPosition = constrainedMin |
|
maxPosition = constrainedMax |
|
|
|
val position = (constrainedMin * (1-positionPercentage) + constrainedMax * positionPercentage) |
|
.roundToInt() |
|
|
|
val firstPlaceable = firstMeasurable.measure( |
|
if (isHorizontal) { |
|
constraints.copy( |
|
minWidth = 0, |
|
maxWidth = position |
|
) |
|
} else { |
|
constraints.copy( |
|
minHeight = 0, |
|
maxHeight = position |
|
) |
|
} |
|
) |
|
|
|
val secondPlaceablePosition = position + splitterSize |
|
val secondAvailableSize = (maxConstraintOnMainAxis - secondPlaceablePosition).coerceAtLeast(0) |
|
|
|
val secondPlaceable = secondMeasurable.measure( |
|
if (isHorizontal) { |
|
constraints.copy( |
|
minWidth = 0, |
|
maxWidth = secondAvailableSize |
|
) |
|
} else { |
|
constraints.copy( |
|
minHeight = 0, |
|
maxHeight = secondAvailableSize |
|
) |
|
} |
|
) |
|
|
|
val handlePlaceable = handleMeasurable.measure(splitterConstraints) |
|
val handleSize = handlePlaceable.valueByDirection(isHorizontal) |
|
// TODO support RTL |
|
val handlePosition = when (splitter.alignment) { |
|
SplitterHandleAlignment.BEFORE -> position + splitterSize - handleSize |
|
SplitterHandleAlignment.ABOVE -> position + (splitterSize - handleSize) / 2 |
|
SplitterHandleAlignment.AFTER -> position |
|
} |
|
|
|
layout(constraints.maxWidth, constraints.maxHeight) { |
|
firstPlaceable.placeRelative(0, 0) |
|
if (isHorizontal) { |
|
secondPlaceable.placeRelative(secondPlaceablePosition, 0) |
|
splitterPlaceable.placeRelative(position, 0) |
|
if (moveEnabled) { |
|
handlePlaceable.placeRelative(handlePosition, 0) |
|
} |
|
} else { |
|
secondPlaceable.placeRelative(0, secondPlaceablePosition) |
|
splitterPlaceable.placeRelative(0, position) |
|
if (moveEnabled) { |
|
handlePlaceable.placeRelative(0, handlePosition) |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |