Browse Source

Split pane. Refactoring (#408)

pull/417/head
Igor Demin 4 years ago committed by GitHub
parent
commit
1b634255e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      components/.gitignore
  2. 6
      components/SplitPane/common/build.gradle.kts
  3. 6
      components/SplitPane/common/src/commonMain/kotlin/org/jetbrains/compose/splitpane/ExperimentalSplitPaneApi.kt
  4. 3
      components/SplitPane/common/src/commonMain/kotlin/org/jetbrains/compose/splitpane/SplitPane.kt
  5. 15
      components/SplitPane/common/src/commonMain/kotlin/org/jetbrains/compose/splitpane/SplitPaneDSL.kt
  6. 23
      components/SplitPane/common/src/commonMain/kotlin/org/jetbrains/compose/splitpane/SplitPaneState.kt
  7. 2
      components/SplitPane/common/src/commonMain/kotlin/org/jetbrains/compose/splitpane/Splitter.kt
  8. 17
      components/SplitPane/common/src/desktopMain/kotlin/org/jetbrains/compose/splitpane/DesktopSplitPane.kt
  9. 6
      components/SplitPane/common/src/desktopMain/kotlin/org/jetbrains/compose/splitpane/DesktopSplitter.kt
  10. 5
      components/SplitPane/desktop/build.gradle.kts
  11. 24
      components/SplitPane/desktop/src/jvmMain/kotlin/org/jetbrains/compose/splitpane/demo/Main.kt

15
components/.gitignore vendored

@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
build/
/captures
.externalNativeBuild
.cxx

6
components/SplitPane/common/build.gradle.kts

@ -1,4 +1,5 @@
import org.jetbrains.compose.compose import org.jetbrains.compose.compose
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
kotlin("multiplatform") kotlin("multiplatform")
@ -19,3 +20,8 @@ kotlin {
named("desktopMain") {} named("desktopMain") {}
} }
} }
// TODO it seems that argument isn't applied to the common sourceSet. Figure out why
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
}

6
components/SplitPane/common/src/commonMain/kotlin/org/jetbrains/compose/splitpane/ExperimentalSplitPaneApi.kt

@ -0,0 +1,6 @@
package org.jetbrains.compose.splitpane
@MustBeDocumented
@Retention(value = AnnotationRetention.BINARY)
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
annotation class ExperimentalSplitPaneApi

3
components/SplitPane/common/src/commonMain/kotlin/org/jetbrains/compose/splitpane/SplitPane.kt

@ -20,6 +20,7 @@ internal data class MinimalSizes(
* @param content a block which describes the content. Inside this block you can use methods like * @param content a block which describes the content. Inside this block you can use methods like
* [SplitPaneScope.first], [SplitPaneScope.second], to describe parts of split pane. * [SplitPaneScope.first], [SplitPaneScope.second], to describe parts of split pane.
*/ */
@ExperimentalSplitPaneApi
@Composable @Composable
fun VerticalSplitPane( fun VerticalSplitPane(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -54,6 +55,7 @@ fun VerticalSplitPane(
* @param content a block which describes the content. Inside this block you can use methods like * @param content a block which describes the content. Inside this block you can use methods like
* [SplitPaneScope.first], [SplitPaneScope.second], to describe parts of split pane. * [SplitPaneScope.first], [SplitPaneScope.second], to describe parts of split pane.
*/ */
@ExperimentalSplitPaneApi
@Composable @Composable
fun HorizontalSplitPane( fun HorizontalSplitPane(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -85,6 +87,7 @@ fun HorizontalSplitPane(
* @param isHorizontal describes is it horizontal or vertical split pane * @param isHorizontal describes is it horizontal or vertical split pane
* @param splitPaneState the state object to be used to control or observe the split pane state * @param splitPaneState the state object to be used to control or observe the split pane state
*/ */
@OptIn(ExperimentalSplitPaneApi::class)
internal expect fun defaultSplitter( internal expect fun defaultSplitter(
isHorizontal: Boolean, isHorizontal: Boolean,
splitPaneState: SplitPaneState splitPaneState: SplitPaneState

15
components/SplitPane/common/src/commonMain/kotlin/org/jetbrains/compose/splitpane/SplitPaneDSL.kt

@ -11,6 +11,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
/** Receiver scope which is used by [HorizontalSplitPane] and [VerticalSplitPane] */ /** Receiver scope which is used by [HorizontalSplitPane] and [VerticalSplitPane] */
@ExperimentalSplitPaneApi
interface SplitPaneScope { interface SplitPaneScope {
/** /**
@ -44,12 +45,14 @@ interface SplitPaneScope {
} }
/** Receiver scope which is used by [SplitterScope] */ /** Receiver scope which is used by [SplitterScope] */
@ExperimentalSplitPaneApi
interface HandleScope { interface HandleScope {
/** allow mark composable as movable handle */ /** allow mark composable as movable handle */
fun Modifier.markAsHandle(): Modifier fun Modifier.markAsHandle(): Modifier
} }
/** Receiver scope which is used by [SplitPaneScope] */ /** Receiver scope which is used by [SplitPaneScope] */
@ExperimentalSplitPaneApi
interface SplitterScope { interface SplitterScope {
/** /**
* Set up visible part of splitter. This part will be measured and placed between split pane * Set up visible part of splitter. This part will be measured and placed between split pane
@ -65,7 +68,7 @@ interface SplitterScope {
* *
* @param alignment alignment of handle according to [visiblePart] could be: * @param alignment alignment of handle according to [visiblePart] could be:
* * [SplitterHandleAlign.BEFORE] if you place handle before [visiblePart], * * [SplitterHandleAlign.BEFORE] if you place handle before [visiblePart],
* * [SplitterHandleAlign.ABOVE] if you place handle above [visiblePart] (will be centred) * * [SplitterHandleAlign.ABOVE] if you place handle above [visiblePart] (will be centered)
* * and [SplitterHandleAlign.AFTER] if you place handle after [visiblePart]. * * and [SplitterHandleAlign.AFTER] if you place handle after [visiblePart].
* *
* @param content composable item content provider. Uses [HandleScope] to allow mark any provided composable part * @param content composable item content provider. Uses [HandleScope] to allow mark any provided composable part
@ -74,10 +77,11 @@ interface SplitterScope {
*/ */
fun handle( fun handle(
alignment: SplitterHandleAlign = SplitterHandleAlign.ABOVE, alignment: SplitterHandleAlign = SplitterHandleAlign.ABOVE,
content: HandleScope.() -> @Composable () -> Unit content: @Composable HandleScope.() -> Unit
) )
} }
@OptIn(ExperimentalSplitPaneApi::class)
internal class HandleScopeImpl( internal class HandleScopeImpl(
private val containerScope: SplitPaneScopeImpl private val containerScope: SplitPaneScopeImpl
) : HandleScope { ) : HandleScope {
@ -91,6 +95,7 @@ internal class HandleScopeImpl(
} }
} }
@OptIn(ExperimentalSplitPaneApi::class)
internal class SplitterScopeImpl( internal class SplitterScopeImpl(
private val containerScope: SplitPaneScopeImpl private val containerScope: SplitPaneScopeImpl
) : SplitterScope { ) : SplitterScope {
@ -101,15 +106,16 @@ internal class SplitterScopeImpl(
override fun handle( override fun handle(
alignment: SplitterHandleAlign, alignment: SplitterHandleAlign,
content: HandleScope.() -> @Composable () -> Unit content: @Composable HandleScope.() -> Unit
) { ) {
containerScope.handle = HandleScopeImpl(containerScope).content() containerScope.handle = { HandleScopeImpl(containerScope).content() }
containerScope.alignment = alignment containerScope.alignment = alignment
} }
} }
private typealias ComposableSlot = @Composable () -> Unit private typealias ComposableSlot = @Composable () -> Unit
@OptIn(ExperimentalSplitPaneApi::class)
internal class SplitPaneScopeImpl( internal class SplitPaneScopeImpl(
internal val isHorizontal: Boolean, internal val isHorizontal: Boolean,
internal val splitPaneState: SplitPaneState internal val splitPaneState: SplitPaneState
@ -168,6 +174,7 @@ internal class SplitPaneScopeImpl(
* @param moveEnabled the initial value for [SplitPaneState.moveEnabled] * @param moveEnabled the initial value for [SplitPaneState.moveEnabled]
* @param interactionState the initial value for [SplitPaneState.interactionState] * @param interactionState the initial value for [SplitPaneState.interactionState]
* */ * */
@ExperimentalSplitPaneApi
@Composable @Composable
fun rememberSplitPaneState( fun rememberSplitPaneState(
initialPositionPercentage: Float = 0f, initialPositionPercentage: Float = 0f,

23
components/SplitPane/common/src/commonMain/kotlin/org/jetbrains/compose/splitpane/SplitPaneState.kt

@ -2,31 +2,22 @@ package org.jetbrains.compose.splitpane
import androidx.compose.foundation.Interaction import androidx.compose.foundation.Interaction
import androidx.compose.foundation.InteractionState import androidx.compose.foundation.InteractionState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.structuralEqualityPolicy import androidx.compose.runtime.setValue
@ExperimentalSplitPaneApi
class SplitPaneState( class SplitPaneState(
initialPositionPercentage: Float, initialPositionPercentage: Float,
moveEnabled: Boolean, moveEnabled: Boolean,
private val interactionState: InteractionState private val interactionState: InteractionState
) { ) {
private var _moveEnabled = mutableStateOf(moveEnabled, structuralEqualityPolicy()) var moveEnabled by mutableStateOf(moveEnabled)
internal set
var moveEnabled: Boolean
get() = _moveEnabled.value
set(newValue) {
_moveEnabled.value = newValue
}
private val _positionPercentage = mutableStateOf(initialPositionPercentage, structuralEqualityPolicy()) var positionPercentage by mutableStateOf(initialPositionPercentage)
internal set
var positionPercentage: Float
get() = _positionPercentage.value
internal set(newPosition) {
_positionPercentage.value = newPosition
}
internal var minPosition: Float = 0f internal var minPosition: Float = 0f

2
components/SplitPane/common/src/commonMain/kotlin/org/jetbrains/compose/splitpane/Splitter.kt

@ -2,12 +2,14 @@ package org.jetbrains.compose.splitpane
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ExperimentalSplitPaneApi
enum class SplitterHandleAlign { enum class SplitterHandleAlign {
BEFORE, BEFORE,
ABOVE, ABOVE,
AFTER AFTER
} }
@OptIn(ExperimentalSplitPaneApi::class)
internal data class Splitter( internal data class Splitter(
val measuredPart: @Composable () -> Unit, val measuredPart: @Composable () -> Unit,
val handlePart: @Composable () -> Unit = measuredPart, val handlePart: @Composable () -> Unit = measuredPart,

17
components/SplitPane/common/src/desktopMain/kotlin/org/jetbrains/compose/splitpane/DesktopSplitPane.kt

@ -11,6 +11,7 @@ private fun Constraints.maxByDirection(isHorizontal: Boolean): Int = if (isHoriz
private fun Constraints.minByDirection(isHorizontal: Boolean): Int = if (isHorizontal) minWidth else minHeight private fun Constraints.minByDirection(isHorizontal: Boolean): Int = if (isHorizontal) minWidth else minHeight
private fun Placeable.valueByDirection(isHorizontal: Boolean): Int = if (isHorizontal) width else height private fun Placeable.valueByDirection(isHorizontal: Boolean): Int = if (isHorizontal) width else height
@OptIn(ExperimentalSplitPaneApi::class)
@Composable @Composable
internal actual fun SplitPane( internal actual fun SplitPane(
modifier: Modifier, modifier: Modifier,
@ -50,10 +51,7 @@ internal actual fun SplitPane(
if (maxPosition != constrainedMax) { if (maxPosition != constrainedMax) {
maxPosition = maxPosition =
if ((firstPlaceableMinimalSize + secondPlaceableMinimalSize).value < constraints.maxByDirection( if ((firstPlaceableMinimalSize + secondPlaceableMinimalSize).value < constraints.maxByDirection(isHorizontal)) {
isHorizontal
)
) {
constrainedMax constrainedMax
} else { } else {
minPosition minPosition
@ -85,7 +83,8 @@ internal actual fun SplitPane(
) )
val splitterPlaceable = measurables[1].measure(constraints) val splitterPlaceable = measurables[1].measure(constraints)
val secondPlaceablePosition = constrainedPosition + splitterPlaceable.valueByDirection(isHorizontal) val splitterSize = splitterPlaceable.valueByDirection(isHorizontal)
val secondPlaceablePosition = constrainedPosition + splitterSize
val secondPlaceableSize = val secondPlaceableSize =
(constraints.maxByDirection(isHorizontal) - secondPlaceablePosition).coerceIn( (constraints.maxByDirection(isHorizontal) - secondPlaceablePosition).coerceIn(
@ -112,10 +111,12 @@ internal actual fun SplitPane(
) )
val handlePlaceable = measurables[3].measure(constraints) val handlePlaceable = measurables[3].measure(constraints)
val handleSize = handlePlaceable.valueByDirection(isHorizontal)
// TODO support RTL
val handlePosition = when (splitter.align) { val handlePosition = when (splitter.align) {
SplitterHandleAlign.BEFORE -> constrainedPosition - handlePlaceable.valueByDirection(isHorizontal) SplitterHandleAlign.BEFORE -> constrainedPosition - handleSize
SplitterHandleAlign.ABOVE -> constrainedPosition - (handlePlaceable.valueByDirection(isHorizontal) / 2) SplitterHandleAlign.ABOVE -> constrainedPosition + (splitterSize - handleSize) / 2
SplitterHandleAlign.AFTER -> constrainedPosition + handlePlaceable.valueByDirection(isHorizontal) SplitterHandleAlign.AFTER -> constrainedPosition + splitterSize + handleSize
} }
layout(constraints.maxWidth, constraints.maxHeight) { layout(constraints.maxWidth, constraints.maxHeight) {

6
components/SplitPane/common/src/desktopMain/kotlin/org/jetbrains/compose/splitpane/DesktopSplitter.kt

@ -59,8 +59,9 @@ private fun DesktopSplitPaneSeparator(
.background(color) .background(color)
) )
@OptIn(ExperimentalSplitPaneApi::class)
@Composable @Composable
private fun DesctopHandle( private fun DesktopHandle(
isHorizontal: Boolean, isHorizontal: Boolean,
splitPaneState: SplitPaneState splitPaneState: SplitPaneState
) = Box( ) = Box(
@ -85,6 +86,7 @@ private fun DesctopHandle(
} }
) )
@OptIn(ExperimentalSplitPaneApi::class)
internal actual fun defaultSplitter( internal actual fun defaultSplitter(
isHorizontal: Boolean, isHorizontal: Boolean,
splitPaneState: SplitPaneState splitPaneState: SplitPaneState
@ -93,7 +95,7 @@ internal actual fun defaultSplitter(
DesktopSplitPaneSeparator(isHorizontal) DesktopSplitPaneSeparator(isHorizontal)
}, },
handlePart = { handlePart = {
DesctopHandle(isHorizontal, splitPaneState) DesktopHandle(isHorizontal, splitPaneState)
} }
) )

5
components/SplitPane/desktop/build.gradle.kts

@ -1,4 +1,5 @@
import org.jetbrains.compose.compose import org.jetbrains.compose.compose
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
kotlin("multiplatform") kotlin("multiplatform")
@ -22,3 +23,7 @@ compose.desktop {
mainClass = "org.jetbrains.compose.splitpane.demo.MainKt" mainClass = "org.jetbrains.compose.splitpane.demo.MainKt"
} }
} }
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
}

24
components/SplitPane/desktop/src/jvmMain/kotlin/org/jetbrains/compose/splitpane/demo/Main.kt

@ -9,8 +9,6 @@ import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -20,9 +18,8 @@ import androidx.compose.ui.composed
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.input.pointer.pointerMoveFilter import androidx.compose.ui.input.pointer.pointerMoveFilter
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import org.jetbrains.compose.splitpane.ExperimentalSplitPaneApi
import org.jetbrains.compose.splitpane.HorizontalSplitPane import org.jetbrains.compose.splitpane.HorizontalSplitPane
import org.jetbrains.compose.splitpane.VerticalSplitPane import org.jetbrains.compose.splitpane.VerticalSplitPane
import org.jetbrains.compose.splitpane.rememberSplitPaneState import org.jetbrains.compose.splitpane.rememberSplitPaneState
@ -44,6 +41,7 @@ private fun Modifier.cursorForHorizontalResize(
) )
} }
@OptIn(ExperimentalSplitPaneApi::class)
fun main() = Window( fun main() = Window(
"SplitPane demo" "SplitPane demo"
) { ) {
@ -77,16 +75,14 @@ fun main() = Window(
) )
} }
handle { handle {
{ Box(
Box( Modifier
Modifier .markAsHandle()
.markAsHandle() .cursorForHorizontalResize()
.cursorForHorizontalResize() .background(SolidColor(Color.Gray), alpha = 0.50f)
.background(SolidColor(Color.Gray), alpha = 0.50f) .width(9.dp)
.width(8.dp) .fillMaxHeight()
.fillMaxHeight() )
)
}
} }
} }
} }

Loading…
Cancel
Save