diff --git a/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/lazy/DefaultSelectableOnKeyEvent.kt b/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/lazy/DefaultSelectableOnKeyEvent.kt index 2e0ecac7d..4e0d08abf 100644 --- a/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/lazy/DefaultSelectableOnKeyEvent.kt +++ b/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/lazy/DefaultSelectableOnKeyEvent.kt @@ -1,17 +1,16 @@ package org.jetbrains.jewel.foundation.lazy -import org.jetbrains.jewel.foundation.lazy.SelectableKey.Selectable import org.jetbrains.jewel.foundation.tree.SelectableColumnOnKeyEvent import kotlin.math.max import kotlin.math.min open class DefaultSelectableOnKeyEvent( override val keybindings: SelectableColumnKeybindings, - private val selectableState: FocusableLazyListState + private val selectableState: SelectableLazyListState ) : SelectableColumnOnKeyEvent { override suspend fun onSelectFirstItem() { - val firstSelectable = selectableState.keys.indexOfFirst { it is Selectable } + val firstSelectable = selectableState.keys.indexOfFirst { it.selectable } if (firstSelectable >= 0) selectableState.selectSingleItem(firstSelectable) } @@ -19,7 +18,7 @@ open class DefaultSelectableOnKeyEvent( if (selectableState.keys.isNotEmpty()) { buildList { for (i in currentIndex downTo 0) { - if (selectableState.keys[i] is Selectable) add(i) + if (selectableState.keys[i].selectable) add(i) } }.let { selectableState.addElementsToSelection(it, it.last()) @@ -28,7 +27,7 @@ open class DefaultSelectableOnKeyEvent( } override suspend fun onSelectLastItem() { - val lastSelectable = selectableState.keys.indexOfLast { it is Selectable } + val lastSelectable = selectableState.keys.indexOfLast { it.selectable } if (lastSelectable >= 0) selectableState.selectSingleItem(lastSelectable) } @@ -37,7 +36,7 @@ open class DefaultSelectableOnKeyEvent( val lastKey = selectableState.keys.lastIndex buildList { for (i in currentIndex..lastKey) { - if (selectableState.keys[i] is Selectable) add(element = i) + if (selectableState.keys[i].selectable) add(element = i) } }.let { selectableState.addElementsToSelection(it) @@ -48,7 +47,7 @@ open class DefaultSelectableOnKeyEvent( override suspend fun onSelectPreviousItem(currentIndex: Int) { if (currentIndex - 1 >= 0) { for (i in currentIndex - 1 downTo 0) { - if (selectableState.keys[i] is Selectable) { + if (selectableState.keys[i].selectable) { selectableState.selectSingleItem(i) break } @@ -112,7 +111,7 @@ open class DefaultSelectableOnKeyEvent( override suspend fun onScrollPageUpAndSelectItem(currentIndex: Int) { val visibleSize = selectableState.layoutInfo.visibleItemsInfo.size val targetIndex = max(currentIndex - visibleSize, 0) - if (selectableState.keys[targetIndex] !is Selectable) { + if (!selectableState.keys[targetIndex].selectable) { selectableState.indexOfPreviousSelectable(currentIndex) ?: selectableState.indexOfNextSelectable(currentIndex)?.let { selectableState.selectSingleItem(it) } @@ -125,7 +124,7 @@ open class DefaultSelectableOnKeyEvent( val visibleSize = selectableState.layoutInfo.visibleItemsInfo.size val targetIndex = max(currentIndex - visibleSize, 0) val indexList = - selectableState.keys.subList(targetIndex, currentIndex).withIndex().filter { it.value is Selectable }.map { it.index }.toList() + selectableState.keys.subList(targetIndex, currentIndex).withIndex().filter { it.value.selectable }.map { it.index }.toList() selectableState.toggleElementsToSelection(indexList) } @@ -133,7 +132,7 @@ open class DefaultSelectableOnKeyEvent( val firstVisible = selectableState.firstVisibleItemIndex val visibleSize = selectableState.layoutInfo.visibleItemsInfo.size val targetIndex = min(firstVisible + visibleSize, selectableState.keys.lastIndex) - if (selectableState.keys[targetIndex] !is Selectable) { + if (!selectableState.keys[targetIndex].selectable) { selectableState.indexOfNextSelectable(currentIndex) ?: selectableState.indexOfPreviousSelectable(currentIndex)?.let { selectableState.selectSingleItem(it) } @@ -147,7 +146,7 @@ open class DefaultSelectableOnKeyEvent( val visibleSize = selectableState.layoutInfo.visibleItemsInfo.size val targetIndex = min(firstVisible + visibleSize, selectableState.keys.lastIndex) val indexList = - selectableState.keys.subList(currentIndex, targetIndex).withIndex().filter { it.value is Selectable }.map { it.index }.toList() + selectableState.keys.subList(currentIndex, targetIndex).withIndex().filter { it.value.selectable }.map { it.index }.toList() selectableState.toggleElementsToSelection(indexList) } diff --git a/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/lazy/FocusableLazyColumn.kt b/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/lazy/FocusableLazyColumn.kt index 3063ae589..407e16103 100644 --- a/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/lazy/FocusableLazyColumn.kt +++ b/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/lazy/FocusableLazyColumn.kt @@ -1,14 +1,8 @@ package org.jetbrains.jewel.foundation.lazy import androidx.compose.foundation.focusable -import androidx.compose.foundation.gestures.FlingBehavior -import androidx.compose.foundation.gestures.ScrollableDefaults import androidx.compose.foundation.gestures.awaitFirstDown -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxWithConstraints -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.lazy.LazyItemScope import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.runtime.Composable @@ -20,54 +14,21 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.structuralEqualityPolicy -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.input.key.KeyEvent import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.unit.dp -import org.jetbrains.jewel.foundation.lazy.FocusableLazyListScopeContainer.Entry -import org.jetbrains.jewel.foundation.lazy.FocusableLazyListState.LastFocusedKeyContainer +import org.jetbrains.jewel.foundation.lazy.SelectableLazyListScopeContainer.Entry +import org.jetbrains.jewel.foundation.lazy.SelectableLazyListState.LastFocusedKeyContainer import org.jetbrains.jewel.foundation.utils.Log -@Composable -fun FocusableLazyColumn( - modifier: Modifier = Modifier, - state: FocusableLazyListState = rememberFocusableLazyListState(), - contentPadding: PaddingValues = PaddingValues(0.dp), - reverseLayout: Boolean = false, - verticalArrangement: Arrangement.Vertical = - if (!reverseLayout) Arrangement.Top else Arrangement.Bottom, - horizontalAlignment: Alignment.Horizontal = Alignment.Start, - flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(), - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - onKeyPressed: KeyEvent.(Int) -> Boolean = { _ -> false }, - content: FocusableLazyListScope.() -> Unit -) { - val entries = remember(content) { FocusableLazyListScopeContainer().apply(content).entries.toList() } -// BaseFocusableLazyColumn( todo should become LazyColumn -// modifier, -// state, -// contentPadding, -// reverseLayout, -// verticalArrangement, -// horizontalAlignment, -// flingBehavior, -// interactionSource, -// onKeyPressed, -// entries -// ) -} - - -fun FocusableLazyListScope.items( +fun SelectableLazyListScope.items( items: List, key: ((item: T) -> Any), contentType: (item: T) -> Any? = { null }, focusable: (item: T) -> Boolean = { true }, - itemContent: @Composable FocusableLazyListScope.(item: T) -> Unit + itemContent: @Composable SelectableLazyListScope.(item: T) -> Unit ) = items( count = items.size, key = { key(items[it]) }, @@ -78,12 +39,12 @@ fun FocusableLazyListScope.items( } @Suppress("unused") -fun FocusableLazyListScope.itemsIndexed( +fun SelectableLazyListScope.itemsIndexed( items: List, key: ((item: T) -> Any), contentType: (item: T) -> Any? = { null }, focusable: (item: T) -> Boolean = { true }, - itemContent: @Composable FocusableLazyItemScope.(item: T, index: Int) -> Unit + itemContent: @Composable SelectableLazyItemScope.(item: T, index: Int) -> Unit ) = repeat(items.size) { index -> item( key = key.invoke(items[index]), @@ -94,10 +55,9 @@ fun FocusableLazyListScope.itemsIndexed( } } - private fun LazyListScope.items( entry: Entry.Items, - state: FocusableLazyListState + state: SelectableLazyListState ) { val lastKey = state.lastFocusedKeyState.value val requesters = Array(entry.count) { if (entry.focusable(it)) FocusRequester() else null } @@ -112,9 +72,9 @@ private fun LazyListScope.items( count = entry.count, key = { if (entry.focusable(it)) { - FocusableKey.Focusable(requesters[it]!!, entry.key?.invoke(it)) + SelectableKey.Focusable(requesters[it]!!, entry.key?.invoke(it), entry.selectable.invoke(it)) } else { - FocusableKey.NotFocusable(entry.key?.invoke(it)) + SelectableKey.NotFocusable(entry.key?.invoke(it), entry.selectable.invoke(it)) } }, contentType = entry.contentType @@ -122,7 +82,7 @@ private fun LazyListScope.items( val itemFinalIndex = entry.innerIndex + itemIndex var hasFocus by remember { mutableStateOf(false) } if (entry.focusable(itemIndex)) { - BoxWithConstraints( + Box( Modifier .focusRequester(requesters[itemIndex]!!) .onFocusChanged { @@ -145,10 +105,10 @@ private fun LazyListScope.items( } } ) { - entry.itemContent(FocusableLazyItemScope(), itemIndex) + entry.itemContent(SelectableLazyItemScope(), itemIndex) } } else { - entry.itemContent(FocusableLazyItemScope(), itemIndex) + entry.itemContent(SelectableLazyItemScope(), itemIndex) } if (state.lastFocusedIndex == itemFinalIndex) { LaunchedEffect(hasFocus) { if (!hasFocus) requesters[itemIndex]!!.requestFocus() } @@ -158,7 +118,7 @@ private fun LazyListScope.items( private fun LazyListScope.singleElement( entry: Entry.Single, - state: FocusableLazyListState + state: SelectableLazyListState ) { if (entry.focusable) { val lastKey = state.lastFocusedKeyState.value @@ -188,23 +148,23 @@ private fun LazyListScope.singleElement( } ) { val isFocused by remember { derivedStateOf(structuralEqualityPolicy()) { state.lastFocusedIndex == entry.innerIndex } } - entry.content(FocusableLazyItemScope(isFocused)) + entry.content(SelectableLazyItemScope(isFocused)) } if (state.lastFocusedIndex == entry.innerIndex) SideEffect { fr.requestFocus() } } if (entry is Entry.Single.Item) { - item(FocusableKey.Focusable(fr, entry.key), entry.contentType, newContent) + item(SelectableKey.Focusable(fr, entry.key, entry.selectable), entry.contentType, newContent) } else { - stickyHeader(FocusableKey.Focusable(fr, entry.key), entry.contentType, newContent) + stickyHeader(SelectableKey.Focusable(fr, entry.key, entry.selectable), entry.contentType, newContent) } } else { if (entry is Entry.Single.Item) { - item(FocusableKey.NotFocusable(entry.key), entry.contentType) { - entry.content(FocusableLazyItemScope()) + item(SelectableKey.NotFocusable(entry.key, entry.selectable), entry.contentType) { + entry.content(SelectableLazyItemScope()) } } else { - stickyHeader(FocusableKey.NotFocusable(entry.key), entry.contentType) { - entry.content(FocusableLazyItemScope()) + stickyHeader(SelectableKey.NotFocusable(entry.key, entry.selectable), entry.contentType) { + entry.content(SelectableLazyItemScope()) } } } diff --git a/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/lazy/FocusableListState.kt b/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/lazy/FocusableListState.kt index 747fce39c..c6629520f 100644 --- a/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/lazy/FocusableListState.kt +++ b/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/lazy/FocusableListState.kt @@ -4,7 +4,6 @@ import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.gestures.ScrollableState import androidx.compose.foundation.interaction.InteractionSource import androidx.compose.foundation.lazy.LazyItemScope -import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState @@ -28,10 +27,10 @@ interface FocusableState : ScrollableState { val LazyListState.visibleItemsRange get() = firstVisibleItemIndex..firstVisibleItemIndex + layoutInfo.visibleItemsInfo.size -val FocusableLazyListState.visibleItemsRange +val SelectableLazyListState.visibleItemsRange get() = firstVisibleItemIndex..firstVisibleItemIndex + layoutInfo.visibleItemsInfo.size -class FocusableLazyListState( +class SelectableLazyListState( val lazyListState: LazyListState, val selectionMode: SelectionMode = SelectionMode.None, @@ -89,7 +88,7 @@ class FocusableLazyListState( layoutInfo.visibleItemsInfo .find { it.index == itemIndex } ?.key - ?.let { it as? FocusableKey.Focusable } + ?.let { it as? SelectableKey.Focusable } ?.focusRequester ?.requestFocus() } @@ -133,7 +132,7 @@ class FocusableLazyListState( fun indexOfNextSelectable(currentIndex: Int): Int? { if (currentIndex + 1 > keys.lastIndex) return null for (i in currentIndex + 1..keys.lastIndex) { - if (keys[i] is SelectableKey.Selectable) return i + if (keys[i].selectable) return i } return null } @@ -141,7 +140,7 @@ class FocusableLazyListState( fun indexOfPreviousSelectable(currentIndex: Int): Int? { if (currentIndex - 1 < 0) return null for (i in currentIndex - 1 downTo 0) { - if (keys[i] is SelectableKey.Selectable) return i + if (keys[i].selectable) return i } return null } @@ -155,7 +154,7 @@ class FocusableLazyListState( suspend fun selectSingleKey(key: SelectableKey, changeFocus: Boolean = true) { val index = keys.indexOf(key) - if (index > 0 && key is SelectableKey.Selectable) selectSingleItem(index, changeFocus) + if (index > 0 && key.selectable) selectSingleItem(index, changeFocus) lastSelectedIndex = index } @@ -179,7 +178,7 @@ class FocusableLazyListState( suspend fun toggleSelectionKey(key: SelectableKey) { if (selectionMode == SelectionMode.None) return val index = keys.indexOf(key) - if (index > 0 && key is SelectableKey.Selectable) toggleSelection(index) + if (index > 0 && key.selectable) toggleSelection(index) lastSelectedIndex = index } @@ -205,7 +204,7 @@ class FocusableLazyListState( private fun addKeyToSelectionMap(keyIndex: Int) { if (selectionMode == SelectionMode.None) return - if (keys[keyIndex] is SelectableKey.Selectable) { + if (keys[keyIndex].selectable) { selectedIdsMap[keys[keyIndex]] = keyIndex } } @@ -283,7 +282,7 @@ sealed interface SelectableKey { ) : SelectableKey } -interface FocusableLazyItemScope : LazyItemScope { +interface SelectableLazyItemScope : LazyItemScope { val isSelected: Boolean val isFocused: Boolean @@ -293,7 +292,7 @@ enum class SelectionMode { None, Single, Multiple } -internal class FocusableLazyListScopeContainer(lazyListScope: LazyListScope) : FocusableLazyListScope { +internal class SelectableLazyListScopeContainer() : SelectableLazyListScope { private var lastIndex = 0 @@ -306,7 +305,7 @@ internal class FocusableLazyListScopeContainer(lazyListScope: LazyListScope) : F val contentType: Any? val focusable: Boolean val selectable: Boolean - val content: @Composable FocusableLazyItemScope.() -> Unit + val content: @Composable SelectableLazyItemScope.() -> Unit data class Item( override val key: Any, @@ -314,7 +313,7 @@ internal class FocusableLazyListScopeContainer(lazyListScope: LazyListScope) : F override val contentType: Any?, override val focusable: Boolean, override val selectable: Boolean, - override val content: @Composable FocusableLazyItemScope.() -> Unit + override val content: @Composable SelectableLazyItemScope.() -> Unit ) : Single data class StickyHeader( @@ -323,7 +322,7 @@ internal class FocusableLazyListScopeContainer(lazyListScope: LazyListScope) : F override val contentType: Any?, override val focusable: Boolean, override val selectable: Boolean, - override val content: @Composable FocusableLazyItemScope.() -> Unit + override val content: @Composable SelectableLazyItemScope.() -> Unit ) : Single } @@ -334,7 +333,7 @@ internal class FocusableLazyListScopeContainer(lazyListScope: LazyListScope) : F val innerIndex: Int, val focusable: (index: Int) -> Boolean, val selectable: (index: Int) -> Boolean, - val itemContent: @Composable FocusableLazyItemScope.(index: Int) -> Unit + val itemContent: @Composable SelectableLazyItemScope.(index: Int) -> Unit ) : Entry } @@ -345,7 +344,7 @@ internal class FocusableLazyListScopeContainer(lazyListScope: LazyListScope) : F contentType: Any?, focusable: Boolean, selectable: Boolean, - content: @Composable FocusableLazyItemScope.() -> Unit + content: @Composable SelectableLazyItemScope.() -> Unit ) { entries.add(Entry.Single.Item(key, lastIndex++, contentType, focusable, selectable, content)) } @@ -356,7 +355,7 @@ internal class FocusableLazyListScopeContainer(lazyListScope: LazyListScope) : F contentType: (index: Int) -> Any?, focusable: (index: Int) -> Boolean, selectable: (index: Int) -> Boolean, - itemContent: @Composable FocusableLazyItemScope.(index: Int) -> Unit + itemContent: @Composable SelectableLazyItemScope.(index: Int) -> Unit ) { entries.add( element = Entry.Items( @@ -378,24 +377,24 @@ internal class FocusableLazyListScopeContainer(lazyListScope: LazyListScope) : F contentType: Any?, focusable: Boolean, selectable: Boolean, - content: @Composable FocusableLazyItemScope.() -> Unit + content: @Composable SelectableLazyItemScope.() -> Unit ) { entries.add(Entry.Single.StickyHeader(key, lastIndex++, contentType, focusable, selectable, content)) } } @Composable -fun rememberFocusableLazyListState( +fun rememberSelectableLazyListState( firstVisibleItemIndex: Int = 0, firstVisibleItemScrollOffset: Int = 0 -) = remember { FocusableLazyListState(LazyListState(firstVisibleItemIndex, firstVisibleItemScrollOffset)) } +) = remember { SelectableLazyListState(LazyListState(firstVisibleItemIndex, firstVisibleItemScrollOffset)) } @Composable -fun LazyItemScope.FocusableLazyItemScope(isFocused: Boolean = false, isSelected: Boolean = false): FocusableLazyItemScope = - FocusableLazyItemScopeDelegate(this, isFocused, isSelected) +fun LazyItemScope.SelectableLazyItemScope(isFocused: Boolean = false, isSelected: Boolean = false): SelectableLazyItemScope = + SelectableLazyItemScopeDelegate(this, isFocused, isSelected) -internal class FocusableLazyItemScopeDelegate( +internal class SelectableLazyItemScopeDelegate( private val delegate: LazyItemScope, override val isFocused: Boolean, override val isSelected: Boolean -) : FocusableLazyItemScope, LazyItemScope by delegate +) : SelectableLazyItemScope, LazyItemScope by delegate diff --git a/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/lazy/SelectableLazyColumn.kt b/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/lazy/SelectableLazyColumn.kt index c49662ad6..1b40e0f35 100644 --- a/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/lazy/SelectableLazyColumn.kt +++ b/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/lazy/SelectableLazyColumn.kt @@ -1,5 +1,6 @@ package org.jetbrains.jewel.foundation.lazy +import androidx.compose.foundation.focusable import androidx.compose.foundation.gestures.FlingBehavior import androidx.compose.foundation.gestures.ScrollableDefaults import androidx.compose.foundation.interaction.MutableInteractionSource @@ -7,6 +8,8 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf @@ -17,6 +20,8 @@ import androidx.compose.runtime.structuralEqualityPolicy import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.input.key.KeyEvent +import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.isCtrlPressed import androidx.compose.ui.input.pointer.onPointerEvent @@ -25,11 +30,10 @@ import kotlinx.coroutines.launch import org.jetbrains.jewel.foundation.tree.DefaultSelectableLazyColumnKeyActions import org.jetbrains.jewel.foundation.tree.KeyBindingScopedActions import org.jetbrains.jewel.foundation.utils.Log -import java.util.UUID -@Composable fun SelectableLazyColumn( +fun SelectableLazyColumn( modifier: Modifier = Modifier, - state: FocusableLazyListState = rememberFocusableLazyListState(), + state: SelectableLazyListState = rememberSelectableLazyListState(), contentPadding: PaddingValues = PaddingValues(0.dp), reverseLayout: Boolean = false, verticalArrangement: Arrangement.Vertical = if (!reverseLayout) Arrangement.Top else Arrangement.Bottom, @@ -37,37 +41,70 @@ import java.util.UUID flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, keyActions: KeyBindingScopedActions = DefaultSelectableLazyColumnKeyActions(state), - content: FocusableLazyListScope.() -> Unit + content: SelectableLazyListScope.() -> Unit ) { LaunchedEffect(keyActions) { state.attachKeybindings(keyActions) } + BaseSelectableLazyColumn( + modifier = modifier, + state = state, + contentPadding = contentPadding, + reverseLayout = reverseLayout, + verticalArrangement = verticalArrangement, + horizontalAlignment = horizontalAlignment, + flingBehavior = flingBehavior, + interactionSource = interactionSource, + keyActions = keyActions.handleOnKeyEvent(rememberCoroutineScope()), + content = content + ) +} - val container = remember(content) { FocusableLazyListStateDelegate(state).apply(content) } - val uiId = remember { UUID.randomUUID().toString() } - LaunchedEffect(container) { state.attachKeys(container.keys, uiId) } -// BaseFocusableLazyColumn(// todo should become LazyColumn( -// modifier = modifier, -// state = state, -// contentPadding = contentPadding, -// reverseLayout = reverseLayout, -// verticalArrangement = verticalArrangement, -// horizontalAlignment = horizontalAlignment, -// flingBehavior = flingBehavior, -// interactionSource = interactionSource, -// onKeyPressed = keyActions.handleOnKeyEvent(rememberCoroutineScope()), -// content = container.entries -// ) +@Composable +internal fun BaseSelectableLazyColumn( + modifier: Modifier = Modifier, + state: SelectableLazyListState = rememberSelectableLazyListState(), + contentPadding: PaddingValues = PaddingValues(0.dp), + reverseLayout: Boolean = false, + verticalArrangement: Arrangement.Vertical = if (!reverseLayout) Arrangement.Top else Arrangement.Bottom, + horizontalAlignment: Alignment.Horizontal = Alignment.Start, + flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(), + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + keyActions: KeyEvent.(Int) -> Boolean = { false }, + content: SelectableLazyListScope.() -> Unit +) { + val container = remember(content) { SelectableLazyListScopeContainer().apply(content) } +// val uiId = remember { UUID.randomUUID().toString() } +// LaunchedEffect(container) { state.attachKeys(container.keys, uiId) } + LazyColumn( + modifier = modifier + .onPreviewKeyEvent { event -> state.lastFocusedIndex?.let { keyActions.invoke(event, it) } ?: false } + .focusable(interactionSource = interactionSource), + state = state.lazyListState, + contentPadding = contentPadding, + reverseLayout = reverseLayout, + verticalArrangement = verticalArrangement, + horizontalAlignment = horizontalAlignment, + flingBehavior = flingBehavior, + ) { + container.entries.forEach { + when (it) { + SelectableLazyListScopeContainer.Entry.Items -> + } + } + } } -interface FocusableLazyListScope { +fun SelctableLazyListScope.lazyListScope(state: SelectableLazyListState) + +interface SelectableLazyListScope { fun item( key: Any, contentType: Any? = null, focusable: Boolean = true, selectable: Boolean = true, - content: @Composable FocusableLazyItemScope.() -> Unit + content: @Composable SelectableLazyItemScope.() -> Unit ) fun items( @@ -76,7 +113,7 @@ interface FocusableLazyListScope { contentType: (index: Int) -> Any? = { null }, focusable: (index: Int) -> Boolean = { true }, selectable: (index: Int) -> Boolean = { true }, - itemContent: @Composable FocusableLazyItemScope.(index: Int) -> Unit + itemContent: @Composable SelectableLazyItemScope.(index: Int) -> Unit ) fun stickyHeader( @@ -84,18 +121,16 @@ interface FocusableLazyListScope { contentType: Any? = null, focusable: Boolean = false, selectable: Boolean = false, - content: @Composable FocusableLazyItemScope.() -> Unit + content: @Composable SelectableLazyItemScope.() -> Unit ) } -internal class FocusableLazyListStateDelegate( - private val state: FocusableLazyListState -) : FocusableLazyListScope { +internal class SelectableLazyListStateDelegate( + private val state: SelectableLazyListState, private val lazyListScope: LazyListScope +) : SelectableLazyListScope { private val _keys = mutableListOf() - private val delegate = FocusableLazyListScopeContainer(this) - val keys: List get() = _keys @@ -104,7 +139,7 @@ internal class FocusableLazyListStateDelegate( contentType: Any?, focusable: Boolean, selectable: Boolean, - content: @Composable FocusableLazyListScopeContainer.() -> Unit + content: @Composable SelectableLazyItemScope.() -> Unit ) { val selectableKey = if (focusable) { SelectableKey.Focusable(FocusRequester(), key, selectable) @@ -112,7 +147,7 @@ internal class FocusableLazyListStateDelegate( SelectableKey.NotFocusable(key, selectable) } _keys.add(selectableKey) - delegate.item(key, contentType, focusable) { + lazyListScope.item(key, contentType) { val scope = rememberCoroutineScope() if (selectable) { Box( @@ -129,7 +164,7 @@ internal class FocusableLazyListStateDelegate( it.keyboardModifiers.isKeyboardMultiSelectionKeyPressed -> { Log.i("shift pressed on click") scope.launch { - state.onExtendSelectionToKey(SelectableKey.Selectable(key)) + state.onExtendSelectionToKey(selectableKey) } } @@ -137,14 +172,14 @@ internal class FocusableLazyListStateDelegate( Log.i("ctrl pressed on click") state.lastKeyEventUsedMouse = false scope.launch { - state.toggleSelectionKey(SelectableKey.Selectable(key)) + state.toggleSelectionKey(selectableKey) } } else -> { Log.i("single click") scope.launch { - state.selectSingleKey(SelectableKey.Selectable(key)) + state.selectSingleKey(selectableKey) } } } @@ -152,11 +187,10 @@ internal class FocusableLazyListStateDelegate( } ) { val isSelected by remember { derivedStateOf(structuralEqualityPolicy()) { selectableKey in state.selectedIdsMap } } - - FocusableLazyItemScope(isSelected).content() + SelectableLazyItemScope(isSelected) } } else { - FocusableLazyItemScope().content() + SelectableLazyItemScope() } } } @@ -167,10 +201,20 @@ internal class FocusableLazyListStateDelegate( contentType: (index: Int) -> Any?, focusable: (index: Int) -> Boolean, selectable: (index: Int) -> Boolean, - itemContent: @Composable FocusableLazyItemScope.(index: Int) -> Unit + itemContent: @Composable SelectableLazyItemScope.(index: Int) -> Unit ) { + lazyListScope.items( + count = count, + key = { + if (focusable(it)) SelectableKey.Focusable(FocusRequester(), key(it), selectable(it)) else SelectableKey.NotFocusable( + key(it), + selectable(it) + ) + }, + itemContent = itemContent + ) repeat(count) { - item(key(it), contentType(it), focusable(it), selectable(it)) { itemContent(it) } + item(key(it), contentType(it), focusable(it)) { itemContent(it) } } } @@ -179,9 +223,14 @@ internal class FocusableLazyListStateDelegate( contentType: Any?, focusable: Boolean, selectable: Boolean, - content: @Composable FocusableLazyItemScope.() -> Unit + content: @Composable SelectableLazyItemScope.() -> Unit ) { - _keys.add(if (selectable) SelectableKey.Selectable(key) else SelectableKey.NotSelectable(key)) + val selectableKey = if (focusable) { + SelectableKey.Focusable(FocusRequester(), key, selectable) + } else { + SelectableKey.NotFocusable(key, selectable) + } + _keys.add(selectableKey) delegate.item(key, contentType, focusable) { if (selectable) { @@ -201,7 +250,7 @@ internal class FocusableLazyListStateDelegate( Log.i("shift pressed on click") scope.launch { - state.onExtendSelectionToKey(SelectableKey.Selectable(key)) + state.onExtendSelectionToKey(selectableKey) } } @@ -210,14 +259,14 @@ internal class FocusableLazyListStateDelegate( state.lastKeyEventUsedMouse = false scope.launch { - state.toggleSelectionKey(SelectableKey.Selectable(key)) + state.toggleSelectionKey(selectableKey) } } else -> { Log.i("single click") scope.launch { - state.selectSingleKey(SelectableKey.Selectable(key)) + state.selectSingleKey(selectableKey) } } } diff --git a/samples/standalone-new-ui/src/main/kotlin/org/jetbrains/jewel/samples/standalone/expui/Main.kt b/samples/standalone-new-ui/src/main/kotlin/org/jetbrains/jewel/samples/standalone/expui/Main.kt index d2bbc41c6..f509684e6 100644 --- a/samples/standalone-new-ui/src/main/kotlin/org/jetbrains/jewel/samples/standalone/expui/Main.kt +++ b/samples/standalone-new-ui/src/main/kotlin/org/jetbrains/jewel/samples/standalone/expui/Main.kt @@ -45,7 +45,6 @@ import org.jetbrains.compose.splitpane.SplitPaneScope import org.jetbrains.compose.splitpane.rememberSplitPaneState import org.jetbrains.jewel.foundation.lazy.FocusableLazyColumn import org.jetbrains.jewel.foundation.lazy.SelectableLazyColumn -import org.jetbrains.jewel.foundation.lazy.rememberFocusableLazyListState import org.jetbrains.jewel.foundation.lazy.rememberSelectableLazyListState import org.jetbrains.jewel.foundation.tree.Tree import org.jetbrains.jewel.foundation.tree.TreeView @@ -241,7 +240,7 @@ fun AdvancedControls() { } defaultSplitter(localAreaColors.startBorderColor) second { - val focusState = rememberFocusableLazyListState() + val focusState = rememberSelectableLazyListState() Column(Modifier.padding(defaultPadding)) { Label( modifier = Modifier.padding(bottom = defaultPadding),