-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
75 changed files
with
6,293 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
foundation/src/main/kotlin/org/jetbrains/jewel/foundation/OverflowBox.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package org.jetbrains.jewel.foundation | ||
|
||
import androidx.compose.foundation.layout.Box | ||
import androidx.compose.foundation.layout.BoxScope | ||
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.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.layout.layout | ||
import androidx.compose.ui.unit.Constraints | ||
import org.jetbrains.jewel.foundation.modifier.onHover | ||
|
||
@ExperimentalJewelApi | ||
@Composable | ||
public fun OverflowBox( | ||
modifier: Modifier = Modifier, | ||
contentAlignment: Alignment = Alignment.TopStart, | ||
overflowZIndex: Float = 1f, | ||
content: @Composable BoxScope.() -> Unit, | ||
) { | ||
var showOverflow by remember { mutableStateOf(false) } | ||
|
||
Box( | ||
Modifier | ||
.layout { measurable, constraints -> | ||
val predictWidth = measurable.maxIntrinsicWidth(constraints.maxHeight) | ||
val constraintWith = constraints.maxWidth | ||
|
||
val overflowing = showOverflow && predictWidth > constraintWith | ||
|
||
val targetConstraints = | ||
if (overflowing) { | ||
constraints.copy( | ||
minWidth = 0, | ||
maxWidth = Constraints.Infinity, | ||
) | ||
} else { | ||
constraints | ||
} | ||
val zIndex = if (overflowing) overflowZIndex else 0f | ||
|
||
// Trick for overflow layout, I don't know why cell align center without offset. | ||
val offset = if (overflowing) (predictWidth - constraintWith) / 2 else 0 | ||
|
||
val placements = measurable.measure(targetConstraints) | ||
layout(placements.width, placements.height) { | ||
placements.placeRelative(offset, 0, zIndex) | ||
} | ||
}.onHover { | ||
showOverflow = it | ||
}.then(modifier), | ||
contentAlignment = contentAlignment, | ||
content = content, | ||
) | ||
} |
105 changes: 105 additions & 0 deletions
105
foundation/src/main/kotlin/org/jetbrains/jewel/foundation/lazy/draggable/Draggable.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package org.jetbrains.jewel.foundation.lazy.draggable | ||
|
||
import androidx.compose.foundation.gestures.Orientation | ||
import androidx.compose.foundation.gestures.detectDragGestures | ||
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.geometry.Offset | ||
import androidx.compose.ui.graphics.graphicsLayer | ||
import androidx.compose.ui.input.pointer.pointerInput | ||
import androidx.compose.ui.layout.onGloballyPositioned | ||
import androidx.compose.ui.layout.positionInRoot | ||
import androidx.compose.ui.modifier.ModifierLocal | ||
import androidx.compose.ui.modifier.modifierLocalConsumer | ||
import androidx.compose.ui.modifier.modifierLocalOf | ||
import androidx.compose.ui.modifier.modifierLocalProvider | ||
import androidx.compose.ui.zIndex | ||
|
||
internal val ModifierLocalDraggableLayoutOffset = modifierLocalOf { Offset.Zero } | ||
|
||
internal fun Modifier.draggableLayout(): Modifier = | ||
composed { | ||
var offset by remember { mutableStateOf(Offset.Zero) } | ||
|
||
this | ||
.onGloballyPositioned { | ||
offset = it.positionInRoot() | ||
}.modifierLocalProvider(ModifierLocalDraggableLayoutOffset) { | ||
offset | ||
} | ||
} | ||
|
||
internal fun Modifier.draggingGestures( | ||
stateLocal: ModifierLocal<LazyLayoutDraggingState<*>>, | ||
key: Any?, | ||
): Modifier = | ||
composed { | ||
var state by remember { mutableStateOf<LazyLayoutDraggingState<*>?>(null) } | ||
var itemOffset by remember { mutableStateOf(Offset.Zero) } | ||
var layoutOffset by remember { mutableStateOf(Offset.Zero) } | ||
|
||
modifierLocalConsumer { | ||
state = stateLocal.current | ||
layoutOffset = ModifierLocalDraggableLayoutOffset.current | ||
}.then( | ||
if (state != null) { | ||
Modifier | ||
.onGloballyPositioned { | ||
itemOffset = it.positionInRoot() | ||
}.pointerInput(Unit) { | ||
detectDragGestures(onDrag = { change, offset -> | ||
change.consume() | ||
state?.onDrag(offset) | ||
}, onDragStart = { | ||
state?.onDragStart(key, it + itemOffset - layoutOffset) | ||
}, onDragEnd = { | ||
state?.onDragInterrupted() | ||
}, onDragCancel = { | ||
state?.onDragInterrupted() | ||
}) | ||
} | ||
} else { | ||
Modifier | ||
}, | ||
) | ||
} | ||
|
||
internal fun Modifier.draggingOffset( | ||
stateLocal: ModifierLocal<LazyLayoutDraggingState<*>>, | ||
key: Any?, | ||
orientation: Orientation? = null, | ||
): Modifier = | ||
composed { | ||
var state by remember { mutableStateOf<LazyLayoutDraggingState<*>?>(null) } | ||
val dragging = state?.draggingItemKey == key | ||
|
||
this | ||
.modifierLocalConsumer { | ||
state = stateLocal.current | ||
}.then( | ||
if (state != null && dragging) { | ||
Modifier.zIndex(2f).graphicsLayer( | ||
translationX = | ||
if (orientation == Orientation.Vertical) { | ||
0f | ||
} else { | ||
state?.draggingItemOffsetTransformX | ||
?: 0f | ||
}, | ||
translationY = | ||
if (orientation == Orientation.Horizontal) { | ||
0f | ||
} else { | ||
state?.draggingItemOffsetTransformY | ||
?: 0f | ||
}, | ||
) | ||
} else { | ||
Modifier | ||
}, | ||
) | ||
} |
94 changes: 94 additions & 0 deletions
94
.../src/main/kotlin/org/jetbrains/jewel/foundation/lazy/draggable/LazyLayoutDraggingState.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package org.jetbrains.jewel.foundation.lazy.draggable | ||
|
||
import androidx.compose.foundation.interaction.MutableInteractionSource | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.runtime.mutableStateOf | ||
import androidx.compose.runtime.setValue | ||
import androidx.compose.ui.geometry.Offset | ||
import androidx.compose.ui.geometry.Size | ||
|
||
public abstract class LazyLayoutDraggingState<T> { | ||
public var draggingItemOffsetTransformX: Float by mutableStateOf(0f) | ||
|
||
public var draggingItemOffsetTransformY: Float by mutableStateOf(0f) | ||
|
||
public var draggingItemKey: Any? by mutableStateOf(null) | ||
|
||
public var initialOffset: Offset = Offset.Zero | ||
|
||
public var draggingOffset: Offset = Offset.Zero | ||
|
||
internal val interactionSource: MutableInteractionSource = MutableInteractionSource() | ||
|
||
public fun onDragStart( | ||
key: Any?, | ||
offset: Offset, | ||
) { | ||
draggingItemKey = key | ||
initialOffset = offset | ||
draggingOffset = Offset.Zero | ||
draggingItemOffsetTransformX = 0f | ||
draggingItemOffsetTransformY = 0f | ||
|
||
println("Drag start with '$key' at '$offset'") | ||
} | ||
|
||
public fun onDrag(offset: Offset) { | ||
draggingItemOffsetTransformX += offset.x | ||
draggingItemOffsetTransformY += offset.y | ||
draggingOffset += offset | ||
|
||
val draggingItem = getItemWithKey(draggingItemKey ?: return) ?: return | ||
val hoverItem = getReplacingItem(draggingItem) | ||
|
||
if (hoverItem != null && draggingItem.key != hoverItem.key) { | ||
val targetOffset = | ||
if (draggingItem.index < hoverItem.index) { | ||
val maxOffset = hoverItem.offset + Offset(hoverItem.size.width, hoverItem.size.height) | ||
maxOffset - Offset(draggingItem.size.width, draggingItem.size.height) | ||
} else { | ||
hoverItem.offset | ||
} | ||
|
||
val changedOffset = draggingItem.offset - targetOffset | ||
|
||
println("Drag '${draggingItem.key}(${draggingItem.size})' at ${draggingItem.offset}") | ||
println("Over '${hoverItem.key}(${hoverItem.size})' at ${hoverItem.offset}") | ||
println("Into $targetOffset With $changedOffset") | ||
|
||
if (moveItem(draggingItem.key, hoverItem.key)) { | ||
draggingItemOffsetTransformX += changedOffset.x | ||
draggingItemOffsetTransformY += changedOffset.y | ||
} | ||
} | ||
} | ||
|
||
public fun onDragInterrupted() { | ||
println("Drag end") | ||
|
||
draggingItemKey = null | ||
initialOffset = Offset.Zero | ||
draggingOffset = Offset.Zero | ||
draggingItemOffsetTransformX = 0f | ||
draggingItemOffsetTransformY = 0f | ||
} | ||
|
||
public abstract fun canMove(key: Any?): Boolean | ||
|
||
public abstract fun moveItem( | ||
from: Any?, | ||
to: Any?, | ||
): Boolean | ||
|
||
public abstract fun getReplacingItem(draggingItem: T): T? | ||
|
||
public abstract fun getItemWithKey(key: Any): T? | ||
|
||
public abstract val T.offset: Offset | ||
|
||
public abstract val T.size: Size | ||
|
||
public abstract val T.index: Int | ||
|
||
public abstract val T.key: Any? | ||
} |
3 changes: 3 additions & 0 deletions
3
foundation/src/main/kotlin/org/jetbrains/jewel/foundation/lazy/selectable/SelectionEvent.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package org.jetbrains.jewel.foundation.lazy.selectable | ||
|
||
public interface SelectionEvent |
62 changes: 62 additions & 0 deletions
62
...dation/src/main/kotlin/org/jetbrains/jewel/foundation/lazy/selectable/SelectionManager.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package org.jetbrains.jewel.foundation.lazy.selectable | ||
|
||
import androidx.compose.foundation.focusGroup | ||
import androidx.compose.foundation.focusable | ||
import androidx.compose.foundation.interaction.MutableInteractionSource | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.Stable | ||
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.input.pointer.PointerKeyboardModifiers | ||
import androidx.compose.ui.input.pointer.isCtrlPressed | ||
import androidx.compose.ui.input.pointer.isMetaPressed | ||
import androidx.compose.ui.input.pointer.isShiftPressed | ||
import androidx.compose.ui.modifier.ModifierLocalConsumer | ||
import androidx.compose.ui.modifier.ModifierLocalModifierNode | ||
import androidx.compose.ui.modifier.ModifierLocalReadScope | ||
import androidx.compose.ui.modifier.modifierLocalConsumer | ||
import androidx.compose.ui.modifier.modifierLocalOf | ||
import androidx.compose.ui.modifier.modifierLocalProvider | ||
import androidx.compose.ui.node.DelegatingNode | ||
|
||
public interface SelectionManager { | ||
public val interactionSource: MutableInteractionSource | ||
|
||
public fun isSelectable(itemKey: Any?): Boolean | ||
|
||
public fun isSelected(itemKey: Any?): Boolean | ||
|
||
public fun handleEvent(event: SelectionEvent) | ||
|
||
public fun clearSelection() | ||
} | ||
|
||
internal val ModifierLocalSelectionManager = modifierLocalOf<SelectionManager?> { null } | ||
|
||
public fun Modifier.selectionManager(manager: SelectionManager): Modifier = | ||
focusable(interactionSource = manager.interactionSource) | ||
.focusGroup() | ||
.modifierLocalProvider(ModifierLocalSelectionManager) { | ||
manager | ||
} | ||
|
||
public fun Modifier.selectionManagerConsumer(factory: @Composable (SelectionManager) -> Modifier): Modifier = | ||
composed { | ||
var manager by remember { mutableStateOf<SelectionManager?>(null) } | ||
|
||
this | ||
.modifierLocalConsumer { | ||
manager = ModifierLocalSelectionManager.current | ||
}.then(if (manager != null) factory(manager!!) else Modifier) | ||
} | ||
|
||
internal fun PointerKeyboardModifiers.selectionType(): SelectionType = | ||
when { | ||
this.isCtrlPressed || this.isMetaPressed -> SelectionType.Multi | ||
this.isShiftPressed -> SelectionType.Contiguous | ||
else -> SelectionType.Normal | ||
} |
21 changes: 21 additions & 0 deletions
21
foundation/src/main/kotlin/org/jetbrains/jewel/foundation/lazy/selectable/SelectionMode.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package org.jetbrains.jewel.foundation.lazy.selectable | ||
|
||
/** | ||
* Specifies the selection mode for a selectable lazy list. | ||
*/ | ||
public enum class SelectionMode { | ||
/** | ||
* No selection is allowed. | ||
*/ | ||
None, | ||
|
||
/** | ||
* Only a single cell can be selected. | ||
*/ | ||
Single, | ||
|
||
/** | ||
* Multiple cells can be selected. | ||
*/ | ||
Multiple, | ||
} |
7 changes: 7 additions & 0 deletions
7
foundation/src/main/kotlin/org/jetbrains/jewel/foundation/lazy/selectable/SelectionType.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package org.jetbrains.jewel.foundation.lazy.selectable | ||
|
||
public enum class SelectionType { | ||
Normal, | ||
Contiguous, | ||
Multi, | ||
} |
29 changes: 29 additions & 0 deletions
29
...ion/src/main/kotlin/org/jetbrains/jewel/foundation/lazy/table/AwaitFirstLayoutModifier.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package org.jetbrains.jewel.foundation.lazy.table | ||
|
||
import androidx.compose.ui.layout.LayoutCoordinates | ||
import androidx.compose.ui.layout.OnGloballyPositionedModifier | ||
import kotlin.coroutines.Continuation | ||
import kotlin.coroutines.resume | ||
import kotlin.coroutines.suspendCoroutine | ||
|
||
internal class AwaitFirstLayoutModifier : OnGloballyPositionedModifier { | ||
|
||
private var wasPositioned = false | ||
private var continuation: Continuation<Unit>? = null | ||
|
||
suspend fun waitForFirstLayout() { | ||
if (!wasPositioned) { | ||
val oldContinuation = continuation | ||
suspendCoroutine { continuation = it } | ||
oldContinuation?.resume(Unit) | ||
} | ||
} | ||
|
||
override fun onGloballyPositioned(coordinates: LayoutCoordinates) { | ||
if (!wasPositioned) { | ||
wasPositioned = true | ||
continuation?.resume(Unit) | ||
continuation = null | ||
} | ||
} | ||
} |
Oops, something went wrong.