Skip to content

Commit

Permalink
refactor: Reimplement navigation system
Browse files Browse the repository at this point in the history
feat: Back handler (gui esc)
  • Loading branch information
0ffz committed Mar 8, 2025
1 parent 524a78c commit 1f877f2
Show file tree
Hide file tree
Showing 14 changed files with 219 additions and 80 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.mineinabyss.guiy.example.gui

import com.mineinabyss.guiy.viewmodel.GuiyViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import org.bukkit.OfflinePlayer
import java.util.*

interface Routes {
object Home : Routes

object Search : Routes

data class Profile(val player: UUID) : Routes
}

class ProfileViewModel: GuiyViewModel() {
val profile = MutableStateFlow<OfflinePlayer?>(null)
}

//@Composable
//fun NavigationExample() {
// val navController = rememberNavController<Routes>()
// val viewModel = viewModel { ProfileViewModel() }
// NavHost(navController, startDestination = Home) {
// when (it) {
// Home -> HomeScreen(
// search = { navController.navigate(Search) },
// openProfile = { navController.navigate(Profile(it))}
// )
// Search -> TextInput(selectPlayer = { player ->
// viewModel.profile.update { player }
// navController.popBackStack()
// })
//
// is Profile -> ProfileScreen()
// }
// }
//}
//
//@Composable
//fun HomeScreen(
// profileViewModel: ProfileViewModel = viewModel(),
// search: () -> Unit,
// openProfile: (UUID) -> Unit,
//) = Chest("Home Screen") {
// val current = profileViewModel.profile.collectAsState().value
// if(current == null) Button(search) {
// Text("Find a player")
// }
// else Button(onClick = { openProfile(current) }) { Text("Current player: $current") }
//}
//
//@Composable
//fun TextInput(selectPlayer: (OfflinePlayer) -> Unit) {
// var input by remember { mutableStateOf("") }
// Anvil(
// "Search by player name",
// onTextChanged = { input = it },
// inputLeft = { Text("") },
// output = { Text("Search for $input") },
// onSubmit = {
// val player = Bukkit.getOfflinePlayerIfCached(input) ?: return@Anvil
// selectPlayer(player)
// }
// )
//}
//
//@Composable
//fun ProfileScreen() = Chest("Profile Screen") {
//
//}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import org.bukkit.inventory.Inventory
fun Anvil(
title: String,
onTextChanged: (String) -> Unit,
onClose: InventoryCloseScope.() -> Unit = { exit() },
onClose: InventoryCloseScope.() -> Unit = { back() },
onSubmit: (String) -> Unit = {},
inputLeft: @Composable () -> Unit = { InvisibleItem() },
inputRight: @Composable () -> Unit = { InvisibleItem() },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const val MAX_CHEST_HEIGHT = 6
fun Chest(
title: String,
modifier: Modifier = Modifier,
onClose: InventoryCloseScope.() -> Unit = { exit() },
onClose: InventoryCloseScope.() -> Unit = { back() },
content: @Composable () -> Unit,
) {
val titleMM = rememberMiniMsg(title)
Expand All @@ -48,7 +48,7 @@ fun Chest(
fun Chest(
title: Component,
modifier: Modifier = Modifier,
onClose: InventoryCloseScope.() -> Unit = { exit() },
onClose: InventoryCloseScope.() -> Unit = { back() },
content: @Composable () -> Unit,
) {
val holder: GuiyInventoryHolder = LocalInventoryHolder.current
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.mineinabyss.guiy.inventory.LocalClickHandler
import com.mineinabyss.guiy.inventory.LocalGuiyOwner
import com.mineinabyss.guiy.modifiers.click.ClickScope
import com.mineinabyss.guiy.modifiers.drag.DragScope
import com.mineinabyss.guiy.navigation.LocalBackGestureDispatcher
import com.mineinabyss.idofront.time.ticks
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
Expand Down Expand Up @@ -58,6 +59,7 @@ fun InventoryHolder(content: @Composable () -> Unit) {
fun rememberInventoryHolder(): GuiyInventoryHolder {
val clickHandler = LocalClickHandler.current
val owner = LocalGuiyOwner.current
val backDispatcher = LocalBackGestureDispatcher.current
val viewers by owner.viewers.collectAsState()
return remember(clickHandler, viewers) {
object : GuiyInventoryHolder() {
Expand All @@ -77,6 +79,10 @@ fun rememberInventoryHolder(): GuiyInventoryHolder {
override fun exit() {
owner.exit()
}

override fun back() {
backDispatcher?.onBack() ?: exit()
}
}
inventory.onClose.invoke(scope)
if (!owner.exitScheduled) guiyPlugin.launch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ abstract class GuiyInventoryHolder : InventoryHolder {

fun setActiveInventory(inventory: GuiyInventory) = _activeInventory.update { inventory }

fun closeIfActive(inventory: Inventory) = _activeInventory.update {
if(it?.inventory == inventory) null else it
}

abstract fun processClick(scope: ClickScope, event: Cancellable)
abstract fun processDrag(scope: DragScope)

Expand Down
3 changes: 3 additions & 0 deletions src/main/kotlin/com/mineinabyss/guiy/inventory/GuiyOwner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import com.mineinabyss.guiy.layout.LayoutNode
import com.mineinabyss.guiy.modifiers.Constraints
import com.mineinabyss.guiy.modifiers.click.ClickScope
import com.mineinabyss.guiy.modifiers.drag.DragScope
import com.mineinabyss.guiy.navigation.BackGestureDispatcher
import com.mineinabyss.guiy.navigation.LocalBackGestureDispatcher
import com.mineinabyss.guiy.nodes.GuiyNodeApplier
import com.mineinabyss.guiy.viewmodel.GuiyViewModel
import kotlinx.coroutines.*
Expand Down Expand Up @@ -120,6 +122,7 @@ class GuiyOwner(
composition.setContent {
CompositionLocalProvider(
LocalGuiyOwner provides this,
LocalBackGestureDispatcher provides BackGestureDispatcher(),
LocalClickHandler provides object : ClickHandler {
override fun processClick(scope: ClickScope): ClickResult {
val slot = scope.slot
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ import org.bukkit.entity.Player
interface InventoryCloseScope {
val player: Player
fun exit()
fun back()
}
38 changes: 38 additions & 0 deletions src/main/kotlin/com/mineinabyss/guiy/navigation/BackHandler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.mineinabyss.guiy.navigation

import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.staticCompositionLocalOf

class BackGestureDispatcher {
val listeners = mutableListOf<() -> Unit>()
val activeListener get() = listeners.lastOrNull()

fun addListener(listener: () -> Unit) {
listeners.add(listener)
}

fun removeListener(listener: () -> Unit) {
listeners.remove(listener)
}

fun onBack() {
activeListener?.invoke()
}
}

val LocalBackGestureDispatcher = staticCompositionLocalOf<BackGestureDispatcher?> { null }

/**
* A listener to back events (usually a menu being closed with `esc`).
*/
@Composable
fun BackHandler(onBack: () -> Unit) {
val dispatcher = LocalBackGestureDispatcher.current ?: return
DisposableEffect(dispatcher) {
dispatcher.addListener(onBack)
onDispose {
dispatcher.removeListener(onBack)
}
}
}
33 changes: 33 additions & 0 deletions src/main/kotlin/com/mineinabyss/guiy/navigation/NavController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.mineinabyss.guiy.navigation

import androidx.compose.runtime.Composable
import com.mineinabyss.guiy.viewmodel.GuiyViewModel
import com.mineinabyss.guiy.viewmodel.viewModel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed

class NavController() : GuiyViewModel() {
private val screens = MutableStateFlow(listOf<NavRoute>())
val route: StateFlow<NavRoute?> = screens
.map { it.lastOrNull() }
.stateIn(viewModelScope, WhileSubscribed(stopTimeoutMillis = 5000), null)

fun popBackStack() = screens.update { it.dropLast(1) }

fun navigate(route: NavRoute) = screens.update { it + route }

fun <T: Any> navigate(route: T) = navigate(NavRoute.of(route))

fun reset() {
screens.update { listOf() }
}

// fun refresh() {
// val screen = screen
// screens.remove(screen)
// open(screen ?: default())
// }
}

@Composable
fun rememberNavController() = viewModel { NavController() }
10 changes: 10 additions & 0 deletions src/main/kotlin/com/mineinabyss/guiy/navigation/NavGraph.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.mineinabyss.guiy.navigation

import androidx.compose.runtime.Composable

data class NavGraph(
val destinations: Map<String, @Composable (NavRoute) -> Unit>,
val start: NavRoute,
) {
fun getDestination(route: NavRoute) = destinations[route.route]
}
16 changes: 16 additions & 0 deletions src/main/kotlin/com/mineinabyss/guiy/navigation/NavGraphBuilder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.mineinabyss.guiy.navigation

import androidx.compose.runtime.Composable

class NavGraphBuilder {
val destinations = mutableMapOf<String, @Composable (NavRoute) -> Unit>()
fun build(startDestination: NavRoute): NavGraph = NavGraph(
destinations, startDestination
)
}

inline fun <reified T> NavGraphBuilder.composable(crossinline content: @Composable (T) -> Unit) {
destinations[NavRoute.routeFor(T::class)] = {
content(it.data as T)
}
}
19 changes: 19 additions & 0 deletions src/main/kotlin/com/mineinabyss/guiy/navigation/NavHost.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.mineinabyss.guiy.navigation

import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember

@Composable
fun <T: Any> NavHost(
navController: NavController,
startDestination: T,
builder: NavGraphBuilder.() -> Unit,
) {
BackHandler { navController.popBackStack() }
val graph = remember(builder, navController, startDestination) {
NavGraphBuilder().apply(builder).build(NavRoute.of(startDestination))
}
val route = navController.route.collectAsState().value ?: graph.start
graph.getDestination(route)?.invoke(route) ?: error("No navigation destination for ${route.route} was found")
}
15 changes: 15 additions & 0 deletions src/main/kotlin/com/mineinabyss/guiy/navigation/NavRoute.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.mineinabyss.guiy.navigation

import androidx.compose.runtime.Immutable
import kotlin.reflect.KClass

@Immutable
data class NavRoute(
val route: String,
val data: Any?,
) {
companion object {
fun routeFor(kClass: KClass<*>) = "/${kClass.qualifiedName}"
fun of(data: Any) = NavRoute(routeFor(data::class), data)
}
}
77 changes: 0 additions & 77 deletions src/main/kotlin/com/mineinabyss/guiy/navigation/Navigator.kt

This file was deleted.

0 comments on commit 1f877f2

Please sign in to comment.