Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ class OneConfigUIScreen @JvmOverloads constructor(
}

private fun warmRoute(route: Any) {
runCatching { if (shell.navigation.isReady) shell.navigation.navigate(route) }
runCatching { if (shell.navigation.isReady) shell.navigation.navigate(route, clearSearch = false) }
}

private fun markClosed() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
"oneconfig.preferences.show_opening_page_animation.description": "Plays the page animation when the menu is first opened.",

"oneconfig.preferences.instant_search.title": "Instant Search",
"oneconfig.preferences.instant_search.description": "Focuses the search bar when the OneConfig menu opens.",
"oneconfig.preferences.instant_search.description": "Focuses the search bar when the OneConfig menu opens or you change pages.",

"oneconfig.preferences.search_keybind.title": "Search Keybind",
"oneconfig.preferences.search_keybind.description": "Focuses the search bar in the OneConfig menu and HUD editor.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.layout.boundsInRoot
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.unit.Density
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
Expand All @@ -33,6 +34,7 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withTimeoutOrNull
import org.apache.logging.log4j.LogManager
import org.polyfrost.oneconfig.internal.OneConfigConfig
import org.polyfrost.oneconfig.internal.ui.components.LocalSearchFieldFocused
import org.polyfrost.oneconfig.internal.ui.components.RetainedVisibility
import org.polyfrost.oneconfig.internal.ui.hud.screens.HudDragLayer
import org.polyfrost.oneconfig.internal.ui.navigation.graph.ModsGraph
Expand Down Expand Up @@ -128,7 +130,13 @@ fun OneConfigInterface(
else if (opened) onCloseRequest()
}

val requestClose: () -> Unit = { visible = false }
val searchFocused = remember { mutableStateOf(false) }
val focusManager = LocalFocusManager.current
val requestClose: () -> Unit = {
// commit any field still being edited before configs save, but keep search focused for reopening
if (!searchFocused.value) focusManager.clearFocus()
visible = false
}
val requestOpen: () -> Unit = { visible = true }

SideEffect {
Expand All @@ -138,6 +146,7 @@ fun OneConfigInterface(

CompositionLocalProvider(
LocalCloseRequest provides requestClose,
LocalSearchFieldFocused provides searchFocused,
LocalShell provides shell,
LocalShellNavController provides navController,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.PointerIcon
import androidx.compose.ui.input.pointer.pointerHoverIcon
import androidx.compose.ui.unit.dp
import org.polyfrost.oneconfig.internal.ui.themes.LocalTheme

Expand Down Expand Up @@ -46,7 +48,9 @@ fun IconButton(
Icon(
iconName,
color = iconColor,
modifier = Modifier.onClick(interactionSource, enabled, onClick).then(modifier)
modifier = Modifier.onClick(interactionSource, enabled, onClick)
.then(if (enabled) Modifier.pointerHoverIcon(PointerIcon.Hand) else Modifier)
.then(modifier)
// inset the glyph so the outline doesn't hug it; size bordered buttons 8dp larger to keep the icon size
.then(
if (hoverBorder) Modifier.border(1.dp, borderColor, LocalTheme.current.buttonShape).padding(4.dp)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,26 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.remember
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.focus.FocusProperties
import androidx.compose.ui.focus.FocusPropertiesModifierNode
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.PointerInputModifierNode
import androidx.compose.ui.node.currentValueOf
import androidx.compose.ui.unit.IntSize
import org.polyfrost.oneconfig.internal.ui.sound.UiSoundEvent
import org.polyfrost.oneconfig.internal.ui.sound.UiSounds

Expand All @@ -23,15 +33,52 @@ fun rememberInteractionSource() = remember { MutableInteractionSource() }
fun Modifier.onClick(interactionSource: MutableInteractionSource, onClick: () -> Unit) =
onClick(interactionSource, true, onClick)

fun Modifier.onClick(interactionSource: MutableInteractionSource, enabled: Boolean, onClick: () -> Unit) = clickable(
interactionSource = interactionSource,
indication = null,
enabled = enabled,
onClick = {
UiSounds.play(UiSoundEvent.CLICK)
onClick()
fun Modifier.onClick(interactionSource: MutableInteractionSource, enabled: Boolean, onClick: () -> Unit) =
// when disabled it would apply to focusable children instead
(if (enabled) keepSearchFocus() else this).clickable(
interactionSource = interactionSource,
indication = null,
enabled = enabled,
onClick = {
UiSounds.play(UiSoundEvent.CLICK)
onClick()
}
)

val LocalSearchFieldFocused = staticCompositionLocalOf<MutableState<Boolean>?> { null }

/** Clicking this doesn't take focus from the search field */
fun Modifier.keepSearchFocus(): Modifier = this then KeepSearchFocusElement

private object KeepSearchFocusElement : ModifierNodeElement<KeepSearchFocusNode>() {
override fun create() = KeepSearchFocusNode()

override fun update(node: KeepSearchFocusNode) {}

override fun equals(other: Any?) = other === this

override fun hashCode() = System.identityHashCode(this)
}

private class KeepSearchFocusNode :
Modifier.Node(), PointerInputModifierNode, FocusPropertiesModifierNode, CompositionLocalConsumerModifierNode {

// set only while a press is handled, so tab can still focus this
private var pressing = false

override fun onPointerEvent(pointerEvent: PointerEvent, pass: PointerEventPass, bounds: IntSize) {
if (pass == PointerEventPass.Initial) pressing = pointerEvent.type == PointerEventType.Press
else if (pass == PointerEventPass.Final) pressing = false
}

override fun onCancelPointerInput() {
pressing = false
}

override fun applyFocusProperties(focusProperties: FocusProperties) {
if (pressing && currentValueOf(LocalSearchFieldFocused)?.value == true) focusProperties.canFocus = false
}
)
}

/**
* Swallows press and release events before children see them so anything below cannot be clicked, dragged
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ 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.graphics.SolidColor
import androidx.compose.ui.input.pointer.PointerIcon
import androidx.compose.ui.input.pointer.pointerHoverIcon
Expand Down Expand Up @@ -318,6 +319,7 @@ fun GlobalSearchBar() {
val isFocused by interactionSource.collectIsFocusedAsState()

val focusRequester = remember { FocusRequester() }
val searchFocused = LocalSearchFieldFocused.current

LaunchedEffect(searchText.text) { search.update(searchText.text) }
LaunchedEffect(search.query) {
Expand Down Expand Up @@ -366,7 +368,9 @@ fun GlobalSearchBar() {
cursorBrush = SolidColor(LocalTheme.current.textColorSecondary),
textStyle = searchTextStyle,
interactionSource = interactionSource,
modifier = Modifier.trackTextInputFocus().focusRequester(focusRequester)
modifier = Modifier.trackTextInputFocus()
.focusRequester(focusRequester)
.onFocusChanged { searchFocused?.value = it.isFocused }
) { innerTextField ->
Box(
modifier = Modifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.waitForUpOrCancellation
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.layout.Arrangement
Expand Down Expand Up @@ -56,7 +57,6 @@ import androidx.compose.ui.graphics.drawOutline
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.PointerIcon
import androidx.compose.ui.input.pointer.pointerHoverIcon
import androidx.compose.ui.input.pointer.pointerInput
Expand Down Expand Up @@ -275,11 +275,11 @@ fun ColumnScope.ModsGrid(category: ModCategory) {
.weight(1f)
// see overlayActive above: the overlay card has to be able to leave the grid while it exists
.then(if (overlayActive) Modifier else Modifier.clipToBounds())
// cards can't take focus, so clear it on press to unfocus the search field
// cards and their buttons consume their clicks, so only a click on empty space unfocuses the search field
.pointerInput(focusManager) {
awaitEachGesture {
awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Initial)
focusManager.clearFocus()
awaitFirstDown(requireUnconsumed = false)
if (waitForUpOrCancellation() != null) focusManager.clearFocus()
}
}
.reorderContainer(reorderState),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import org.polyfrost.oneconfig.internal.ui.api.ConfigRegistry
import org.polyfrost.oneconfig.internal.ui.components.Text
import org.polyfrost.oneconfig.internal.ui.components.keepSearchFocus
import org.polyfrost.oneconfig.internal.ui.components.rememberInteractionSource
import org.polyfrost.oneconfig.internal.ui.shell.PageGutter
import org.polyfrost.oneconfig.internal.ui.themes.Accent
Expand Down Expand Up @@ -78,6 +79,7 @@ private fun ThemeCard(theme: UITheme) {
modifier = Modifier
.background(LocalTheme.current.modCardBackground, LocalTheme.current.backgroundShape)
.border(borderWidth, borderColor, LocalTheme.current.backgroundShape)
.keepSearchFocus()
.clickable(interactionSource, indication = null) {
// Use clickable because onClick always plays CLICK. ThemeRegistry.activate chooses CLICK or OPEN
ThemeRegistry.activate(theme)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ class ShellNavigation internal constructor(
seedRouteCategory(route)
updateArrows()
search.bind(host.currentBackStackEntry?.id, query)
if (clearSearch) instantSearch()
}

fun discard(route: Any, fallback: Any) {
Expand Down Expand Up @@ -409,6 +410,7 @@ class ShellNavigation internal constructor(
applyCategory(currentEntry)
search.bind(host.currentBackStackEntry?.id, currentEntry.searchQuery)
lastRoute = currentEntry.route
instantSearch()
updateArrows()
}

Expand All @@ -430,6 +432,11 @@ class ShellNavigation internal constructor(
updateArrows()
host.navigate(next.route)
search.bind(host.currentBackStackEntry?.id, next.searchQuery)
instantSearch()
}

private fun instantSearch() {
if (OneConfigConfig.instantSearch) search.requestFocus(SearchFocus.SelectAll)
}

/** A route that names a category such as an external deep link overrides the remembered tab */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ oneconfig.preferences.remember_last_page.always=Always
oneconfig.preferences.show_opening_page_animation.title=Show opening page animation
oneconfig.preferences.show_opening_page_animation.description=Plays the page animation when the menu is first opened.
oneconfig.preferences.instant_search.title=Instant Search
oneconfig.preferences.instant_search.description=Focuses the search bar when the OneConfig menu opens.
oneconfig.preferences.instant_search.description=Focuses the search bar when the OneConfig menu opens or you change pages.
oneconfig.preferences.search_keybind.title=Search Keybind
oneconfig.preferences.search_keybind.description=Focuses the search bar in the OneConfig menu and HUD editor.
oneconfig.preferences.flip_top_option_order.title=Flip Top Option Order
Expand Down
Loading