Skip to content
Open
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 @@ -256,7 +256,7 @@ class OneConfigUIScreen @JvmOverloads constructor(
}

private fun warmRoute(route: Any) {
runCatching { if (LocalNavController.isReady) LocalNavController.wrapper.navigate(route) }
runCatching { if (LocalNavController.isReady) LocalNavController.wrapper.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.show_option_action_buttons.title": "Show Option Action Buttons",
"oneconfig.preferences.show_option_action_buttons.description": "Shows a more actions button on active option rows. Right-click options still opens the same menu.",
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 @@ -23,15 +23,16 @@ 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) =
focusProperties { canFocus = false }.clickable(
interactionSource = interactionSource,
indication = null,
enabled = enabled,
onClick = {
UiSounds.play(UiSoundEvent.CLICK)
onClick()
}
)

/**
* 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 @@ -85,6 +85,7 @@ fun NumberChainOption(data: NumberChainOptionData) {
mutableStateOf(data.read().map { ChainEntry(idGen[0]++, it) })
}
var editingId by remember(data.prop) { mutableStateOf(-1) }
var editingValue by remember(data.prop) { mutableStateOf<Float?>(null) }

var draggingIndex by remember { mutableStateOf(-1) }
var dragAccum by remember { mutableStateOf(0f) }
Expand All @@ -108,6 +109,16 @@ fun NumberChainOption(data: NumberChainOptionData) {
entries = data.read().map { ChainEntry(idGen[0]++, it) }
}

// clicks elsewhere in the chain leave the editor focused, so they apply its value themselves
fun closeEditor(): List<ChainEntry> {
val id = editingId
val value = editingValue
editingId = -1
editingValue = null
if (id == -1 || value == null) return entries
return entries.map { if (it.id == id) it.copy(value = value) else it }
}

var lastTickIndex by remember { mutableStateOf(-1) }
val draggableState = rememberDraggableState { delta ->
if (draggingIndex == -1) return@rememberDraggableState
Expand Down Expand Up @@ -186,7 +197,7 @@ fun NumberChainOption(data: NumberChainOptionData) {
},
onDragStopped = {
if (dragTargetIndex != draggingIndex && draggingIndex >= 0) {
val list = entries.toMutableList()
val list = closeEditor().toMutableList()
list.add(dragTargetIndex, list.removeAt(draggingIndex))
commit(list)
}
Expand All @@ -205,11 +216,13 @@ fun NumberChainOption(data: NumberChainOptionData) {
max = data.max,
unit = data.unit,
modifier = Modifier.weight(1f),
onValueChange = { editingValue = it },
onCommit = { value ->
// losing focus commits and can arrive after another row claimed the editor
// so only the row still being edited may write
if (editingId == entry.id) {
editingId = -1
editingValue = null
commit(entries.map { if (it.id == entry.id) it.copy(value = value) else it })
}
},
Expand All @@ -223,7 +236,16 @@ fun NumberChainOption(data: NumberChainOptionData) {
.weight(1f)
.then(
if (!movable) Modifier else Modifier
.onClick(rememberInteractionSource()) { editingId = entry.id }
.onClick(rememberInteractionSource()) {
val pending = closeEditor()
if (pending == entries) {
editingId = entry.id
} else {
// committing gives every entry a new id
commit(pending)
editingId = entries.getOrNull(index)?.id ?: -1
Comment on lines +245 to +246

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 [P2] Preserve the clicked entry across chain normalization

When the setter removes duplicates, as the wWaypoints cycle does, committing can change entry positions as well as IDs. For example, editing 100 to 200 in [-1, 100, 200, 300] and clicking the existing 200 normalizes the chain to [-1, 200, 300]. Reusing index 2 then opens the editor for 300, so subsequent typing modifies the wrong entry. Remap the clicked entry after normalization instead of reusing its previous index.

}
}
.pointerHoverIcon(PointerIcon.Hand)
),
)
Expand All @@ -236,7 +258,7 @@ fun NumberChainOption(data: NumberChainOptionData) {
"close",
modifier = Modifier
.size(14.dp)
.onClick(deleteInteraction) { commit(entries.filterNot { it.id == entry.id }) }
.onClick(deleteInteraction) { commit(closeEditor().filterNot { it.id == entry.id }) }
.pointerHoverIcon(PointerIcon.Hand),
color = if (deleteHovered) theme.textColor else theme.textColorSecondary,
)
Expand All @@ -258,8 +280,8 @@ fun NumberChainOption(data: NumberChainOptionData) {
.clip(ChainShape)
.border(1.dp, theme.borderColor, ChainShape)
.onClick(addInteraction) {
val values = entries.map { it.value }
commit(entries + ChainEntry(idGen[0]++, data.nextValue(values)))
val current = closeEditor()
commit(current + ChainEntry(idGen[0]++, data.nextValue(current.map { it.value })))
}
.pointerHoverIcon(PointerIcon.Hand)
.padding(horizontal = 10.dp, vertical = 7.dp),
Expand All @@ -280,6 +302,7 @@ private fun ChainValueEditor(
max: Float,
unit: String?,
modifier: Modifier,
onValueChange: (Float) -> Unit,
onCommit: (Float) -> Unit,
) {
val theme = LocalTheme.current
Expand All @@ -296,11 +319,15 @@ private fun ChainValueEditor(
// the field keeps room for a caret past the last digit which the unit is shifted back across
val fieldWidth = with(density) { measurer.measure(text, style).size.width.toDp() } + CaretWidth

fun commit() = onCommit(text.toFloatOrNull()?.coerceIn(min, max) ?: initial)
fun parsed() = text.toFloatOrNull()?.coerceIn(min, max) ?: initial
fun commit() = onCommit(parsed())

BasicTextField(
value = text,
onValueChange = { text = filterNumberInput(it) },
onValueChange = {
text = filterNumberInput(it)
onValueChange(parsed())
},
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { commit() }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package org.polyfrost.oneconfig.internal.ui.components.settings
import androidx.compose.animation.animateColorAsState
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.hoverable
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.layout.Box
Expand All @@ -23,6 +25,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextOverflow
Expand Down Expand Up @@ -122,6 +127,7 @@ fun NumberSpinner(
val formatted = if (format != null && !focused) format(value) else null
var text by remember(value) { mutableStateOf(formatSpinnerValue(value)) }

val focusManager = LocalFocusManager.current
val arrowLo = maxOf(min, arrowMin)
val arrowHi = maxOf(arrowLo, minOf(max, arrowMax))

Expand Down Expand Up @@ -171,7 +177,17 @@ fun NumberSpinner(
.padding(start = 8.dp, top = 5.dp, bottom = 5.dp),
)

SpinnerArrows(value, arrowLo, arrowHi, step) { commit(it) }
// arrows can't take focus, so end the edit on press to let onEditEnd apply before the step does
Box(
Modifier.pointerInput(focused) {
awaitEachGesture {
awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Initial)
if (focused) focusManager.clearFocus()
}
}
) {
SpinnerArrows(value, arrowLo, arrowHi, step) { commit(it) }
}
}
}

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 @@ -41,7 +42,6 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.center
Expand Down Expand Up @@ -248,11 +248,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, pass = PointerEventPass.Final)
if (waitForUpOrCancellation(PointerEventPass.Final) != null) focusManager.clearFocus()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 [P2] Wait for release on the Main pointer-event pass

An empty-space click with a small, unconsumed mouse movement can now leave search focused. waitForUpOrCancellation internally awaits Final again after checking the requested pass; passing Final makes that second await receive the next event instead. In a down–move–up sequence, the release is checked only for consumption, leaving the detector suspended without calling clearFocus(). Use the default Main pass or a loop that checks every final-pass event.

}
}
.reorderContainer(reorderState),
Expand Down Expand Up @@ -468,7 +468,6 @@ fun ModCard(
if (interactive) {
// cards never take focus, otherwise they slide in from off screen when scrolled back into view
Modifier
.focusProperties { canFocus = false }
.onClick(interactionSource) { openModCard(mod) }
.pointerHoverIcon(PointerIcon.Hand)
} else {
Expand Down Expand Up @@ -507,7 +506,6 @@ fun ModCard(
.then(
if (toggle != null && interactive) {
Modifier
.focusProperties { canFocus = false }
.onClick(footerInteractions) {
if (!enabled && toggle.needsSetup()) {
open()
Expand Down Expand Up @@ -653,7 +651,6 @@ private fun FavoriteStar(
.then(
if (interactive) {
Modifier
.focusProperties { canFocus = false }
.onClick(interactionSource) { ModFavorites.toggle(mod.id) }
.pointerHoverIcon(PointerIcon.Hand)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.input.pointer.PointerIcon
import androidx.compose.ui.input.pointer.pointerHoverIcon
import androidx.compose.ui.res.painterResource
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)
.focusProperties { canFocus = false }
.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 @@ -11,6 +11,7 @@ import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import androidx.navigation.NavHostController
import org.polyfrost.oneconfig.internal.OneConfigConfig
import org.polyfrost.oneconfig.internal.ui.navigation.graph.ModConfigRoute
import org.polyfrost.oneconfig.internal.ui.navigation.graph.ModsGraph

Expand Down Expand Up @@ -179,7 +180,10 @@ object LocalNavController {
forwardStack.clear()
backStack.addLast(currentEntry.withCurrentSearch())
currentEntry = Entry(route)
if (clearSearch) ShellState.searchQuery = ""
if (clearSearch) {
ShellState.searchQuery = ""
instantSearch()
}
ShellState.lastRoute = route
ShellState.lastNavWasBack = false
seedRouteCategory(route)
Expand Down Expand Up @@ -232,6 +236,7 @@ object LocalNavController {
applyCategory(currentEntry)
ShellState.searchQuery = currentEntry.searchQuery
ShellState.lastRoute = currentEntry.route
instantSearch()
updateArrows()
}

Expand All @@ -251,10 +256,15 @@ object LocalNavController {
ShellState.searchQuery = next.searchQuery
ShellState.lastRoute = next.route
ShellState.lastNavWasBack = false
instantSearch()
updateArrows()
host.navigate(next.route)
}

private fun instantSearch() {
if (OneConfigConfig.instantSearch) ShellState.searchFocus = SearchFocus.SelectAll
}

/** A route that names a category such as an external deep link overrides the remembered tab */
private fun seedRouteCategory(route: Any) {
val modConfig = route as? ModConfigRoute ?: return
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.flip_top_option_order.title=Flip Top Option Order
oneconfig.preferences.flip_top_option_order.description=Moves the Edit HUD and Global Search buttons to the bottom of the sidebar, above your account.
oneconfig.preferences.search_distance.title=Search Distance
Expand Down
Loading