Igor Demin
3 years ago
committed by
GitHub
25 changed files with 505 additions and 438 deletions
@ -1,21 +0,0 @@
|
||||
import org.jetbrains.compose.compose |
||||
|
||||
plugins { |
||||
kotlin("multiplatform") |
||||
id("org.jetbrains.compose") |
||||
} |
||||
|
||||
kotlin { |
||||
jvm { |
||||
withJava() |
||||
} |
||||
sourceSets { |
||||
named("jvmMain") { |
||||
dependencies { |
||||
implementation(compose.desktop.currentOs) |
||||
implementation(project("common")) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
@ -1,29 +1,29 @@
|
||||
import org.jetbrains.compose.compose |
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile |
||||
|
||||
plugins { |
||||
kotlin("multiplatform") |
||||
id("org.jetbrains.compose") |
||||
} |
||||
|
||||
kotlin { |
||||
jvm {} |
||||
sourceSets { |
||||
named("jvmMain") { |
||||
dependencies { |
||||
implementation(compose.desktop.currentOs) |
||||
implementation(project(":SplitPane:common")) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
compose.desktop { |
||||
application { |
||||
mainClass = "org.jetbrains.compose.splitpane.demo.MainKt" |
||||
} |
||||
} |
||||
|
||||
tasks.withType<KotlinCompile>().configureEach { |
||||
kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" |
||||
import org.jetbrains.compose.compose |
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile |
||||
|
||||
plugins { |
||||
kotlin("multiplatform") |
||||
id("org.jetbrains.compose") |
||||
} |
||||
|
||||
kotlin { |
||||
jvm {} |
||||
sourceSets { |
||||
named("jvmMain") { |
||||
dependencies { |
||||
implementation(compose.desktop.currentOs) |
||||
implementation(project(":SplitPane:library")) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
compose.desktop { |
||||
application { |
||||
mainClass = "org.jetbrains.compose.splitpane.demo.MainKt" |
||||
} |
||||
} |
||||
|
||||
tasks.withType<KotlinCompile>().configureEach { |
||||
kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" |
||||
} |
@ -1,91 +1,91 @@
|
||||
package org.jetbrains.compose.splitpane.demo |
||||
|
||||
import androidx.compose.desktop.DesktopTheme |
||||
import androidx.compose.desktop.LocalAppWindow |
||||
import androidx.compose.desktop.Window |
||||
import androidx.compose.foundation.background |
||||
import androidx.compose.foundation.layout.Box |
||||
import androidx.compose.foundation.layout.fillMaxHeight |
||||
import androidx.compose.foundation.layout.fillMaxSize |
||||
import androidx.compose.foundation.layout.width |
||||
import androidx.compose.material.MaterialTheme |
||||
import androidx.compose.runtime.getValue |
||||
import androidx.compose.runtime.mutableStateOf |
||||
import androidx.compose.runtime.remember |
||||
import androidx.compose.runtime.setValue |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.composed |
||||
import androidx.compose.ui.graphics.Color |
||||
import androidx.compose.ui.graphics.SolidColor |
||||
import androidx.compose.ui.input.pointer.pointerMoveFilter |
||||
import androidx.compose.ui.unit.dp |
||||
import org.jetbrains.compose.splitpane.ExperimentalSplitPaneApi |
||||
import org.jetbrains.compose.splitpane.HorizontalSplitPane |
||||
import org.jetbrains.compose.splitpane.VerticalSplitPane |
||||
import org.jetbrains.compose.splitpane.rememberSplitPaneState |
||||
import java.awt.Cursor |
||||
|
||||
private fun Modifier.cursorForHorizontalResize( |
||||
): Modifier = composed { |
||||
var isHover by remember { mutableStateOf(false) } |
||||
|
||||
if (isHover) { |
||||
LocalAppWindow.current.window.cursor = Cursor(Cursor.E_RESIZE_CURSOR) |
||||
} else { |
||||
LocalAppWindow.current.window.cursor = Cursor.getDefaultCursor() |
||||
} |
||||
|
||||
pointerMoveFilter( |
||||
onEnter = { isHover = true; true }, |
||||
onExit = { isHover = false; true } |
||||
) |
||||
} |
||||
|
||||
@OptIn(ExperimentalSplitPaneApi::class) |
||||
fun main() = Window( |
||||
"SplitPane demo" |
||||
) { |
||||
MaterialTheme { |
||||
DesktopTheme { |
||||
val splitterState = rememberSplitPaneState() |
||||
val hSplitterState = rememberSplitPaneState() |
||||
HorizontalSplitPane( |
||||
splitPaneState = splitterState |
||||
) { |
||||
first(20.dp) { |
||||
Box(Modifier.background(Color.Red).fillMaxSize()) |
||||
} |
||||
second(50.dp) { |
||||
VerticalSplitPane(splitPaneState = hSplitterState) { |
||||
first(50.dp) { |
||||
Box(Modifier.background(Color.Blue).fillMaxSize()) |
||||
} |
||||
second(20.dp) { |
||||
Box(Modifier.background(Color.Green).fillMaxSize()) |
||||
} |
||||
} |
||||
} |
||||
splitter { |
||||
visiblePart { |
||||
Box( |
||||
Modifier |
||||
.width(1.dp) |
||||
.fillMaxHeight() |
||||
.background(MaterialTheme.colors.background) |
||||
) |
||||
} |
||||
handle { |
||||
Box( |
||||
Modifier |
||||
.markAsHandle() |
||||
.cursorForHorizontalResize() |
||||
.background(SolidColor(Color.Gray), alpha = 0.50f) |
||||
.width(9.dp) |
||||
.fillMaxHeight() |
||||
) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
package org.jetbrains.compose.splitpane.demo |
||||
|
||||
import androidx.compose.desktop.DesktopTheme |
||||
import androidx.compose.desktop.LocalAppWindow |
||||
import androidx.compose.desktop.Window |
||||
import androidx.compose.foundation.background |
||||
import androidx.compose.foundation.layout.Box |
||||
import androidx.compose.foundation.layout.fillMaxHeight |
||||
import androidx.compose.foundation.layout.fillMaxSize |
||||
import androidx.compose.foundation.layout.width |
||||
import androidx.compose.material.MaterialTheme |
||||
import androidx.compose.runtime.getValue |
||||
import androidx.compose.runtime.mutableStateOf |
||||
import androidx.compose.runtime.remember |
||||
import androidx.compose.runtime.setValue |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.composed |
||||
import androidx.compose.ui.graphics.Color |
||||
import androidx.compose.ui.graphics.SolidColor |
||||
import androidx.compose.ui.input.pointer.pointerMoveFilter |
||||
import androidx.compose.ui.unit.dp |
||||
import org.jetbrains.compose.splitpane.ExperimentalSplitPaneApi |
||||
import org.jetbrains.compose.splitpane.HorizontalSplitPane |
||||
import org.jetbrains.compose.splitpane.VerticalSplitPane |
||||
import org.jetbrains.compose.splitpane.rememberSplitPaneState |
||||
import java.awt.Cursor |
||||
|
||||
private fun Modifier.cursorForHorizontalResize( |
||||
): Modifier = composed { |
||||
var isHover by remember { mutableStateOf(false) } |
||||
|
||||
if (isHover) { |
||||
LocalAppWindow.current.window.cursor = Cursor(Cursor.E_RESIZE_CURSOR) |
||||
} else { |
||||
LocalAppWindow.current.window.cursor = Cursor.getDefaultCursor() |
||||
} |
||||
|
||||
pointerMoveFilter( |
||||
onEnter = { isHover = true; true }, |
||||
onExit = { isHover = false; true } |
||||
) |
||||
} |
||||
|
||||
@OptIn(ExperimentalSplitPaneApi::class) |
||||
fun main() = Window( |
||||
"SplitPane demo" |
||||
) { |
||||
MaterialTheme { |
||||
DesktopTheme { |
||||
val splitterState = rememberSplitPaneState() |
||||
val hSplitterState = rememberSplitPaneState() |
||||
HorizontalSplitPane( |
||||
splitPaneState = splitterState |
||||
) { |
||||
first(20.dp) { |
||||
Box(Modifier.background(Color.Red).fillMaxSize()) |
||||
} |
||||
second(50.dp) { |
||||
VerticalSplitPane(splitPaneState = hSplitterState) { |
||||
first(50.dp) { |
||||
Box(Modifier.background(Color.Blue).fillMaxSize()) |
||||
} |
||||
second(20.dp) { |
||||
Box(Modifier.background(Color.Green).fillMaxSize()) |
||||
} |
||||
} |
||||
} |
||||
splitter { |
||||
visiblePart { |
||||
Box( |
||||
Modifier |
||||
.width(1.dp) |
||||
.fillMaxHeight() |
||||
.background(MaterialTheme.colors.background) |
||||
) |
||||
} |
||||
handle { |
||||
Box( |
||||
Modifier |
||||
.markAsHandle() |
||||
.cursorForHorizontalResize() |
||||
.background(SolidColor(Color.Gray), alpha = 0.50f) |
||||
.width(9.dp) |
||||
.fillMaxHeight() |
||||
) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,27 +1,34 @@
|
||||
import org.jetbrains.compose.compose |
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile |
||||
|
||||
plugins { |
||||
kotlin("multiplatform") |
||||
id("org.jetbrains.compose") |
||||
} |
||||
|
||||
kotlin { |
||||
jvm("desktop") |
||||
|
||||
sourceSets { |
||||
named("commonMain") { |
||||
dependencies { |
||||
api(compose.runtime) |
||||
api(compose.foundation) |
||||
api(compose.material) |
||||
} |
||||
} |
||||
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" |
||||
} |
||||
import org.jetbrains.compose.compose |
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile |
||||
|
||||
plugins { |
||||
kotlin("multiplatform") |
||||
id("org.jetbrains.compose") |
||||
id("maven-publish") |
||||
} |
||||
|
||||
kotlin { |
||||
jvm("desktop") |
||||
|
||||
sourceSets { |
||||
named("commonMain") { |
||||
dependencies { |
||||
api(compose.runtime) |
||||
api(compose.foundation) |
||||
api(compose.material) |
||||
} |
||||
} |
||||
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" |
||||
} |
||||
|
||||
configureMavenPublication( |
||||
groupId = "org.jetbrains.compose.components", |
||||
artifactId = "components-splitpane", |
||||
name = "SplitPane for Compose JB" |
||||
) |
@ -1,141 +1,141 @@
|
||||
package org.jetbrains.compose.splitpane |
||||
|
||||
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 Constraints.minByDirection(isHorizontal: Boolean): Int = if (isHorizontal) minWidth else minHeight |
||||
private fun Placeable.valueByDirection(isHorizontal: Boolean): Int = if (isHorizontal) width else height |
||||
|
||||
@OptIn(ExperimentalSplitPaneApi::class) |
||||
@Composable |
||||
internal actual fun SplitPane( |
||||
modifier: Modifier, |
||||
isHorizontal: Boolean, |
||||
splitPaneState: SplitPaneState, |
||||
minimalSizesConfiguration: MinimalSizes, |
||||
first: @Composable () -> Unit, |
||||
second: @Composable () -> Unit, |
||||
splitter: Splitter |
||||
) { |
||||
Layout( |
||||
{ |
||||
first() |
||||
splitter.measuredPart() |
||||
second() |
||||
splitter.handlePart() |
||||
}, |
||||
modifier, |
||||
) { measurables, constraints -> |
||||
with(minimalSizesConfiguration) { |
||||
with(splitPaneState) { |
||||
|
||||
val constrainedMin = constraints.minByDirection(isHorizontal) + firstPlaceableMinimalSize.value |
||||
|
||||
val constrainedMax = |
||||
(constraints.maxByDirection(isHorizontal).toFloat() - secondPlaceableMinimalSize.value).let { |
||||
if (it <= 0 || it <= constrainedMin) { |
||||
constraints.maxByDirection(isHorizontal).toFloat() |
||||
} else { |
||||
it |
||||
} |
||||
} |
||||
|
||||
if (minPosition != constrainedMin) { |
||||
maxPosition = constrainedMin |
||||
} |
||||
|
||||
if (maxPosition != constrainedMax) { |
||||
maxPosition = |
||||
if ((firstPlaceableMinimalSize + secondPlaceableMinimalSize).value < constraints.maxByDirection(isHorizontal)) { |
||||
constrainedMax |
||||
} else { |
||||
minPosition |
||||
} |
||||
} |
||||
|
||||
val constrainedPosition = |
||||
(constraints.maxByDirection(isHorizontal) - (firstPlaceableMinimalSize + secondPlaceableMinimalSize).value).let { |
||||
if (it > 0f) { |
||||
(it * positionPercentage).coerceIn(constrainedMin, constrainedMax).roundToInt() |
||||
} else { |
||||
constrainedMin.roundToInt() |
||||
} |
||||
} |
||||
|
||||
|
||||
val firstPlaceable = measurables[0].measure( |
||||
if (isHorizontal) { |
||||
constraints.copy( |
||||
minWidth = 0, |
||||
maxWidth = constrainedPosition |
||||
) |
||||
} else { |
||||
constraints.copy( |
||||
minHeight = 0, |
||||
maxHeight = constrainedPosition |
||||
) |
||||
} |
||||
) |
||||
|
||||
val splitterPlaceable = measurables[1].measure(constraints) |
||||
val splitterSize = splitterPlaceable.valueByDirection(isHorizontal) |
||||
val secondPlaceablePosition = constrainedPosition + splitterSize |
||||
|
||||
val secondPlaceableSize = |
||||
(constraints.maxByDirection(isHorizontal) - secondPlaceablePosition).coerceIn( |
||||
0, |
||||
if (secondPlaceablePosition < constraints.maxByDirection(isHorizontal)) { |
||||
constraints.maxByDirection(isHorizontal) - secondPlaceablePosition |
||||
} else { |
||||
constraints.maxByDirection(isHorizontal) |
||||
} |
||||
) |
||||
|
||||
val secondPlaceable = measurables[2].measure( |
||||
if (isHorizontal) { |
||||
constraints.copy( |
||||
minWidth = 0, |
||||
maxWidth = secondPlaceableSize |
||||
) |
||||
} else { |
||||
constraints.copy( |
||||
minHeight = 0, |
||||
maxHeight = secondPlaceableSize |
||||
) |
||||
} |
||||
) |
||||
|
||||
val handlePlaceable = measurables[3].measure(constraints) |
||||
val handleSize = handlePlaceable.valueByDirection(isHorizontal) |
||||
// TODO support RTL |
||||
val handlePosition = when (splitter.alignment) { |
||||
SplitterHandleAlignment.BEFORE -> constrainedPosition + splitterSize - handleSize |
||||
SplitterHandleAlignment.ABOVE -> constrainedPosition + (splitterSize - handleSize) / 2 |
||||
SplitterHandleAlignment.AFTER -> constrainedPosition |
||||
} |
||||
|
||||
layout(constraints.maxWidth, constraints.maxHeight) { |
||||
firstPlaceable.place(0, 0) |
||||
if (isHorizontal) { |
||||
secondPlaceable.place(secondPlaceablePosition, 0) |
||||
splitterPlaceable.place(constrainedPosition, 0) |
||||
if (moveEnabled) { |
||||
handlePlaceable.place(handlePosition, 0) |
||||
} |
||||
} else { |
||||
secondPlaceable.place(0, secondPlaceablePosition) |
||||
splitterPlaceable.place(0, constrainedPosition) |
||||
if (moveEnabled) { |
||||
handlePlaceable.place(0, handlePosition) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
package org.jetbrains.compose.splitpane |
||||
|
||||
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 Constraints.minByDirection(isHorizontal: Boolean): Int = if (isHorizontal) minWidth else minHeight |
||||
private fun Placeable.valueByDirection(isHorizontal: Boolean): Int = if (isHorizontal) width else height |
||||
|
||||
@OptIn(ExperimentalSplitPaneApi::class) |
||||
@Composable |
||||
internal actual fun SplitPane( |
||||
modifier: Modifier, |
||||
isHorizontal: Boolean, |
||||
splitPaneState: SplitPaneState, |
||||
minimalSizesConfiguration: MinimalSizes, |
||||
first: @Composable () -> Unit, |
||||
second: @Composable () -> Unit, |
||||
splitter: Splitter |
||||
) { |
||||
Layout( |
||||
{ |
||||
first() |
||||
splitter.measuredPart() |
||||
second() |
||||
splitter.handlePart() |
||||
}, |
||||
modifier, |
||||
) { measurables, constraints -> |
||||
with(minimalSizesConfiguration) { |
||||
with(splitPaneState) { |
||||
|
||||
val constrainedMin = constraints.minByDirection(isHorizontal) + firstPlaceableMinimalSize.value |
||||
|
||||
val constrainedMax = |
||||
(constraints.maxByDirection(isHorizontal).toFloat() - secondPlaceableMinimalSize.value).let { |
||||
if (it <= 0 || it <= constrainedMin) { |
||||
constraints.maxByDirection(isHorizontal).toFloat() |
||||
} else { |
||||
it |
||||
} |
||||
} |
||||
|
||||
if (minPosition != constrainedMin) { |
||||
maxPosition = constrainedMin |
||||
} |
||||
|
||||
if (maxPosition != constrainedMax) { |
||||
maxPosition = |
||||
if ((firstPlaceableMinimalSize + secondPlaceableMinimalSize).value < constraints.maxByDirection(isHorizontal)) { |
||||
constrainedMax |
||||
} else { |
||||
minPosition |
||||
} |
||||
} |
||||
|
||||
val constrainedPosition = |
||||
(constraints.maxByDirection(isHorizontal) - (firstPlaceableMinimalSize + secondPlaceableMinimalSize).value).let { |
||||
if (it > 0f) { |
||||
(it * positionPercentage).coerceIn(constrainedMin, constrainedMax).roundToInt() |
||||
} else { |
||||
constrainedMin.roundToInt() |
||||
} |
||||
} |
||||
|
||||
|
||||
val firstPlaceable = measurables[0].measure( |
||||
if (isHorizontal) { |
||||
constraints.copy( |
||||
minWidth = 0, |
||||
maxWidth = constrainedPosition |
||||
) |
||||
} else { |
||||
constraints.copy( |
||||
minHeight = 0, |
||||
maxHeight = constrainedPosition |
||||
) |
||||
} |
||||
) |
||||
|
||||
val splitterPlaceable = measurables[1].measure(constraints) |
||||
val splitterSize = splitterPlaceable.valueByDirection(isHorizontal) |
||||
val secondPlaceablePosition = constrainedPosition + splitterSize |
||||
|
||||
val secondPlaceableSize = |
||||
(constraints.maxByDirection(isHorizontal) - secondPlaceablePosition).coerceIn( |
||||
0, |
||||
if (secondPlaceablePosition < constraints.maxByDirection(isHorizontal)) { |
||||
constraints.maxByDirection(isHorizontal) - secondPlaceablePosition |
||||
} else { |
||||
constraints.maxByDirection(isHorizontal) |
||||
} |
||||
) |
||||
|
||||
val secondPlaceable = measurables[2].measure( |
||||
if (isHorizontal) { |
||||
constraints.copy( |
||||
minWidth = 0, |
||||
maxWidth = secondPlaceableSize |
||||
) |
||||
} else { |
||||
constraints.copy( |
||||
minHeight = 0, |
||||
maxHeight = secondPlaceableSize |
||||
) |
||||
} |
||||
) |
||||
|
||||
val handlePlaceable = measurables[3].measure(constraints) |
||||
val handleSize = handlePlaceable.valueByDirection(isHorizontal) |
||||
// TODO support RTL |
||||
val handlePosition = when (splitter.alignment) { |
||||
SplitterHandleAlignment.BEFORE -> constrainedPosition + splitterSize - handleSize |
||||
SplitterHandleAlignment.ABOVE -> constrainedPosition + (splitterSize - handleSize) / 2 |
||||
SplitterHandleAlignment.AFTER -> constrainedPosition |
||||
} |
||||
|
||||
layout(constraints.maxWidth, constraints.maxHeight) { |
||||
firstPlaceable.place(0, 0) |
||||
if (isHorizontal) { |
||||
secondPlaceable.place(secondPlaceablePosition, 0) |
||||
splitterPlaceable.place(constrainedPosition, 0) |
||||
if (moveEnabled) { |
||||
handlePlaceable.place(handlePosition, 0) |
||||
} |
||||
} else { |
||||
secondPlaceable.place(0, secondPlaceablePosition) |
||||
splitterPlaceable.place(0, constrainedPosition) |
||||
if (moveEnabled) { |
||||
handlePlaceable.place(0, handlePosition) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,101 +1,101 @@
|
||||
package org.jetbrains.compose.splitpane |
||||
|
||||
import androidx.compose.desktop.LocalAppWindow |
||||
import androidx.compose.foundation.background |
||||
import androidx.compose.foundation.gestures.detectDragGestures |
||||
import androidx.compose.foundation.layout.Box |
||||
import androidx.compose.foundation.layout.fillMaxHeight |
||||
import androidx.compose.foundation.layout.fillMaxWidth |
||||
import androidx.compose.foundation.layout.height |
||||
import androidx.compose.foundation.layout.width |
||||
import androidx.compose.material.MaterialTheme |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.runtime.getValue |
||||
import androidx.compose.runtime.mutableStateOf |
||||
import androidx.compose.runtime.remember |
||||
import androidx.compose.runtime.setValue |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.composed |
||||
import androidx.compose.ui.graphics.Color |
||||
import androidx.compose.ui.input.pointer.consumeAllChanges |
||||
import androidx.compose.ui.input.pointer.pointerInput |
||||
import androidx.compose.ui.input.pointer.pointerMoveFilter |
||||
import androidx.compose.ui.unit.dp |
||||
import java.awt.Cursor |
||||
|
||||
private fun Modifier.cursorForHorizontalResize( |
||||
isHorizontal: Boolean |
||||
): Modifier = composed { |
||||
var isHover by remember { mutableStateOf(false) } |
||||
|
||||
if (isHover) { |
||||
LocalAppWindow.current.window.cursor = Cursor( |
||||
if (isHorizontal) Cursor.E_RESIZE_CURSOR else Cursor.S_RESIZE_CURSOR |
||||
) |
||||
} else { |
||||
LocalAppWindow.current.window.cursor = Cursor.getDefaultCursor() |
||||
} |
||||
pointerMoveFilter( |
||||
onEnter = { isHover = true; true }, |
||||
onExit = { isHover = false; true } |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
private fun DesktopSplitPaneSeparator( |
||||
isHorizontal: Boolean, |
||||
color: Color = MaterialTheme.colors.background |
||||
) = Box( |
||||
Modifier |
||||
.run { |
||||
if (isHorizontal) { |
||||
this.width(1.dp) |
||||
.fillMaxHeight() |
||||
} else { |
||||
this.height(1.dp) |
||||
.fillMaxWidth() |
||||
} |
||||
} |
||||
.background(color) |
||||
) |
||||
|
||||
@OptIn(ExperimentalSplitPaneApi::class) |
||||
@Composable |
||||
private fun DesktopHandle( |
||||
isHorizontal: Boolean, |
||||
splitPaneState: SplitPaneState |
||||
) = Box( |
||||
Modifier |
||||
.pointerInput(splitPaneState) { |
||||
detectDragGestures { change, _ -> |
||||
change.consumeAllChanges() |
||||
splitPaneState.dispatchRawMovement( |
||||
if (isHorizontal) change.position.x else change.position.y |
||||
) |
||||
} |
||||
} |
||||
.cursorForHorizontalResize(isHorizontal) |
||||
.run { |
||||
if (isHorizontal) { |
||||
this.width(8.dp) |
||||
.fillMaxHeight() |
||||
} else { |
||||
this.height(8.dp) |
||||
.fillMaxWidth() |
||||
} |
||||
} |
||||
) |
||||
|
||||
@OptIn(ExperimentalSplitPaneApi::class) |
||||
internal actual fun defaultSplitter( |
||||
isHorizontal: Boolean, |
||||
splitPaneState: SplitPaneState |
||||
): Splitter = Splitter( |
||||
measuredPart = { |
||||
DesktopSplitPaneSeparator(isHorizontal) |
||||
}, |
||||
handlePart = { |
||||
DesktopHandle(isHorizontal, splitPaneState) |
||||
} |
||||
) |
||||
|
||||
package org.jetbrains.compose.splitpane |
||||
|
||||
import androidx.compose.desktop.LocalAppWindow |
||||
import androidx.compose.foundation.background |
||||
import androidx.compose.foundation.gestures.detectDragGestures |
||||
import androidx.compose.foundation.layout.Box |
||||
import androidx.compose.foundation.layout.fillMaxHeight |
||||
import androidx.compose.foundation.layout.fillMaxWidth |
||||
import androidx.compose.foundation.layout.height |
||||
import androidx.compose.foundation.layout.width |
||||
import androidx.compose.material.MaterialTheme |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.runtime.getValue |
||||
import androidx.compose.runtime.mutableStateOf |
||||
import androidx.compose.runtime.remember |
||||
import androidx.compose.runtime.setValue |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.composed |
||||
import androidx.compose.ui.graphics.Color |
||||
import androidx.compose.ui.input.pointer.consumeAllChanges |
||||
import androidx.compose.ui.input.pointer.pointerInput |
||||
import androidx.compose.ui.input.pointer.pointerMoveFilter |
||||
import androidx.compose.ui.unit.dp |
||||
import java.awt.Cursor |
||||
|
||||
private fun Modifier.cursorForHorizontalResize( |
||||
isHorizontal: Boolean |
||||
): Modifier = composed { |
||||
var isHover by remember { mutableStateOf(false) } |
||||
|
||||
if (isHover) { |
||||
LocalAppWindow.current.window.cursor = Cursor( |
||||
if (isHorizontal) Cursor.E_RESIZE_CURSOR else Cursor.S_RESIZE_CURSOR |
||||
) |
||||
} else { |
||||
LocalAppWindow.current.window.cursor = Cursor.getDefaultCursor() |
||||
} |
||||
pointerMoveFilter( |
||||
onEnter = { isHover = true; true }, |
||||
onExit = { isHover = false; true } |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
private fun DesktopSplitPaneSeparator( |
||||
isHorizontal: Boolean, |
||||
color: Color = MaterialTheme.colors.background |
||||
) = Box( |
||||
Modifier |
||||
.run { |
||||
if (isHorizontal) { |
||||
this.width(1.dp) |
||||
.fillMaxHeight() |
||||
} else { |
||||
this.height(1.dp) |
||||
.fillMaxWidth() |
||||
} |
||||
} |
||||
.background(color) |
||||
) |
||||
|
||||
@OptIn(ExperimentalSplitPaneApi::class) |
||||
@Composable |
||||
private fun DesktopHandle( |
||||
isHorizontal: Boolean, |
||||
splitPaneState: SplitPaneState |
||||
) = Box( |
||||
Modifier |
||||
.pointerInput(splitPaneState) { |
||||
detectDragGestures { change, _ -> |
||||
change.consumeAllChanges() |
||||
splitPaneState.dispatchRawMovement( |
||||
if (isHorizontal) change.position.x else change.position.y |
||||
) |
||||
} |
||||
} |
||||
.cursorForHorizontalResize(isHorizontal) |
||||
.run { |
||||
if (isHorizontal) { |
||||
this.width(8.dp) |
||||
.fillMaxHeight() |
||||
} else { |
||||
this.height(8.dp) |
||||
.fillMaxWidth() |
||||
} |
||||
} |
||||
) |
||||
|
||||
@OptIn(ExperimentalSplitPaneApi::class) |
||||
internal actual fun defaultSplitter( |
||||
isHorizontal: Boolean, |
||||
splitPaneState: SplitPaneState |
||||
): Splitter = Splitter( |
||||
measuredPart = { |
||||
DesktopSplitPaneSeparator(isHorizontal) |
||||
}, |
||||
handlePart = { |
||||
DesktopHandle(isHorizontal, splitPaneState) |
||||
} |
||||
) |
||||
|
@ -1,21 +0,0 @@
|
||||
import org.jetbrains.compose.compose |
||||
|
||||
plugins { |
||||
kotlin("multiplatform") |
||||
id("org.jetbrains.compose") |
||||
} |
||||
|
||||
kotlin { |
||||
jvm { |
||||
withJava() |
||||
} |
||||
sourceSets { |
||||
named("jvmMain") { |
||||
dependencies { |
||||
implementation(compose.desktop.currentOs) |
||||
implementation(project("common")) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
@ -0,0 +1,11 @@
|
||||
plugins { |
||||
`kotlin-dsl` |
||||
} |
||||
|
||||
repositories { |
||||
gradlePluginPortal() |
||||
} |
||||
|
||||
dependencies { |
||||
compileOnly(gradleApi()) |
||||
} |
@ -0,0 +1,5 @@
|
||||
pluginManagement { |
||||
repositories { |
||||
gradlePluginPortal() |
||||
} |
||||
} |
@ -0,0 +1,32 @@
|
||||
import org.gradle.api.Project |
||||
import org.gradle.api.publish.PublishingExtension |
||||
import org.gradle.api.publish.maven.MavenPublication |
||||
import org.gradle.kotlin.dsl.configure |
||||
|
||||
fun Project.configureMavenPublication( |
||||
groupId: String, |
||||
artifactId: String, |
||||
name: String |
||||
) { |
||||
extensions.configure<PublishingExtension> { |
||||
publications { |
||||
all { |
||||
this as MavenPublication |
||||
|
||||
this.groupId = groupId |
||||
mppArtifactId = artifactId |
||||
|
||||
pom { |
||||
this.name.set(name) |
||||
url.set("https://github.com/JetBrains/compose-jb") |
||||
licenses { |
||||
license { |
||||
this.name.set("The Apache License, Version 2.0") |
||||
url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,19 @@
|
||||
/* |
||||
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
import org.gradle.api.Project |
||||
import org.gradle.api.publish.maven.MavenPublication |
||||
import java.lang.UnsupportedOperationException |
||||
|
||||
inline fun <reified T> Project.configureIfExists(fn: T.() -> Unit) { |
||||
extensions.findByType(T::class.java)?.fn() |
||||
} |
||||
|
||||
var MavenPublication.mppArtifactId: String |
||||
get() = throw UnsupportedOperationException() |
||||
set(value) { |
||||
val target = this.name |
||||
artifactId = if ("kotlinMultiplatform" in target) value else "$value-$target" |
||||
} |
@ -1,4 +1,7 @@
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 |
||||
android.useAndroidX=true |
||||
android.enableJetifier=true |
||||
kotlin.code.style=official |
||||
kotlin.code.style=official |
||||
|
||||
# __LATEST_COMPOSE_RELEASE_VERSION__ |
||||
compose.version=0.4.0 |
@ -1,4 +1,4 @@
|
||||
include(":VideoPlayer:common") |
||||
include(":VideoPlayer:desktop") |
||||
include(":SplitPane:common") |
||||
include(":SplitPane:desktop") |
||||
include(":VideoPlayer:library") |
||||
include(":VideoPlayer:demo") |
||||
include(":SplitPane:library") |
||||
include(":SplitPane:demo") |
Loading…
Reference in new issue