Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
70ed598
Initial plan
Copilot Feb 3, 2026
6bd0222
Add core GUI framework: props, components, views, context, refs, and DSL
Copilot Feb 3, 2026
81c60f5
Add test GUI view and wire up new framework with test command
Copilot Feb 3, 2026
9adf0ae
Remove old inventory framework and update dependencies
Copilot Feb 3, 2026
4acfd92
Add comprehensive documentation for new GUI framework
Copilot Feb 3, 2026
076b735
Fix code review issues: navigation duplication, ThreadLocal, and prop…
Copilot Feb 3, 2026
1eb2afb
Remove test files, move listener to server, add comprehensive DSL, an…
Copilot Feb 3, 2026
616b5b5
Fix coroutine cancellation and component centering issues
Copilot Feb 3, 2026
c648711
Refactor props to be view-global, add container components, support d…
Copilot Feb 3, 2026
ccaecfe
Improve error messages and simplify lazy prop logic
Copilot Feb 3, 2026
5d5dfc1
Register GuiViewListener in ListenerManager like other listeners
Copilot Feb 3, 2026
6bd3a2b
Fix entityDispatcher usage and implement onInventoryClose handler
Copilot Feb 3, 2026
0764376
Refactor: split classes into separate files and update prop system
Copilot Feb 3, 2026
9c8c8e5
Make ViewContext.getProp suspend to support ComputedProp
Copilot Feb 3, 2026
b6a1832
Split ViewContext into separate files, remove mount/unmount lifecycle…
Copilot Feb 3, 2026
e2d2bb6
Remove PaginationProp, create PaginationComponent, GuiItem, and Slot …
Copilot Feb 3, 2026
67af27d
feat: implement new GUI framework with refactored component and view …
ammodev Feb 3, 2026
eea6cbe
refactor: streamline navigation helper methods and enhance prop manag…
ammodev Feb 3, 2026
4cfdf38
chore: update ABI and bump version [skip ci]
github-actions[bot] Feb 3, 2026
62ea088
Add example GUIs: CounterGui with MutableProp and PaginatedShopGui wi…
Copilot Feb 3, 2026
c56706d
feat: introduce abstract GUI context classes and navigation helper fo…
ammodev Feb 3, 2026
f5e0068
feat: implement ViewManager for GUI view registration and management
ammodev Feb 3, 2026
3031a59
Updated plan: Add component area with priority system for overlapping…
Copilot Feb 3, 2026
5bd44a7
Add component area (startSlot/endSlot) with ComponentPriority enum fo…
Copilot Feb 3, 2026
304ef9d
Update example GUIs to use new component area API with slot embedded …
Copilot Feb 3, 2026
983876b
Refactor components to use ComponentArea interface for flexible shapes
Copilot Feb 3, 2026
6ab9173
refactor: simplify type declarations for UUID in GuiView class
ammodev Feb 3, 2026
e11d8ad
refactor: update component slot handling to use area boundaries for i…
ammodev Feb 3, 2026
b175ee3
Move priority from ComponentArea to Component
Copilot Feb 3, 2026
f825d51
refactor: enhance slot usage in CounterGuiView for improved readabili…
ammodev Feb 3, 2026
94ec968
refactor: optimize shop items initialization and enhance slot usage i…
ammodev Feb 3, 2026
d557cc8
Fix refs and remove unused children system
Copilot Feb 3, 2026
b5c3aa3
Restore child/parent system and investigate ref.current null issue
Copilot Feb 4, 2026
ab476e6
Add debug logging to Ref to trace ref.current null issue
Copilot Feb 4, 2026
8275213
Remove debug logging - refs are working correctly per console logs
Copilot Feb 4, 2026
4e911c1
refactor: simplify attachedRef assignment in ComponentDsl for improve…
ammodev Feb 4, 2026
9e0f753
Fix onUpdate to actually refresh component items in inventory
Copilot Feb 4, 2026
7d34e2f
Refactor PaginationComponent with built-in navigation buttons as chil…
Copilot Feb 4, 2026
ee1d052
Fix ExceptionInInitializerError in PaginatedShopGuiView by making sho…
Copilot Feb 4, 2026
74cd4d5
Fix view initialization order issue in addChild - check if view is in…
Copilot Feb 4, 2026
6e4f795
refactor: streamline slot handling and improve component initializati…
ammodev Feb 4, 2026
05c70c0
refactor: simplify view assignment and update handling in Component c…
ammodev Feb 4, 2026
416e197
Fix children components not being rendered - include them in findComp…
Copilot Feb 4, 2026
681af70
refactor: adjust pagination component slot assignments for improved l…
ammodev Feb 4, 2026
c18e0e2
Merge remote-tracking branch 'origin/copilot/implement-new-gui-framew…
ammodev Feb 4, 2026
61fc99a
refactor: use component's priority for button components in Paginatio…
ammodev Feb 4, 2026
f1219a8
Fix page indicator not updating and add hidden/disabled features to c…
Copilot Feb 4, 2026
b25b929
refactor: remove unused setHidden and setDisabled methods from Compon…
ammodev Feb 4, 2026
cc5e8b9
refactor: simplify page navigation logic in PaginationComponent
ammodev Feb 4, 2026
42349ec
Disable and hide navigation buttons when no further pages available
Copilot Feb 4, 2026
5a83820
Make component updates cascade to children automatically
Copilot Feb 4, 2026
279c9cd
refactor: update button state management in PaginationComponent
ammodev Feb 4, 2026
e7cf885
Merge remote-tracking branch 'origin/copilot/implement-new-gui-framew…
ammodev Feb 4, 2026
bc7f109
refactor: streamline button component management in PaginationComponent
ammodev Feb 4, 2026
fcb17f5
Fix navigation buttons not rendering by setting HIGH priority
Copilot Feb 4, 2026
4896c99
refactor: change PaginatedShopGuiView from object to class and update…
ammodev Feb 4, 2026
ee005bb
Fix child component rendering in empty parent slots and button state …
Copilot Feb 4, 2026
878d640
Replace setHidden/setDisabled method calls with direct property assig…
Copilot Feb 4, 2026
1bc4a1f
Fix components disappearing by removing slot clearing during refresh
Copilot Feb 4, 2026
e182ba1
Fix IllegalAccessError by accessing PaginatedShopGuiView as singleton…
Copilot Feb 4, 2026
9ce41e7
Fix GuiTest to instantiate PaginatedShopGuiView as a class
Copilot Feb 4, 2026
6e0ddce
Fix navigation button visibility by calling update() after state changes
Copilot Feb 4, 2026
1f6b428
Remove duplicate updateNavigationButtonsState calls and manual updates
Copilot Feb 4, 2026
f29aeb7
refactor: remove unnecessary whitespace in PaginationComponent
ammodev Feb 4, 2026
140a876
Fix viewer-specific component updates for coins display and button vi…
Copilot Feb 4, 2026
156f71e
refactor: add Player import to Component and Ref classes
ammodev Feb 4, 2026
4e01595
Fix update cascade timing - update children before refreshing parent …
Copilot Feb 4, 2026
db5b484
Fix rendering system to properly handle child components and slot cle…
Copilot Feb 4, 2026
3c72697
add tostring to everything
ammodev Feb 4, 2026
9ec90a1
refactor: clean up whitespace and enhance component rendering logic
ammodev Feb 4, 2026
e231cfc
Fix button disappearing - remove collectAllSlots, each component refr…
Copilot Feb 4, 2026
0d977d4
refactor: simplify inventory rendering and enhance component update l…
ammodev Feb 4, 2026
ed3008a
refactor: improve slot rendering logic in AbstractGuiView
ammodev Feb 4, 2026
74c0b63
refactor: streamline slot rendering logic and enhance component refre…
ammodev Feb 4, 2026
cf29234
refactor: enhance slot rendering logic to ensure inventory updates co…
ammodev Feb 4, 2026
eca37b0
refactor: add onUpdate callback to ComponentBuilder for enhanced life…
ammodev Feb 4, 2026
6a153be
refactor: update prop handling to use ViewerProp for improved state m…
ammodev Feb 4, 2026
a4feb75
refactor: enhance rendering logic to support first render lifecycle e…
ammodev Feb 4, 2026
b382e2f
refactor: remove unnecessary line break in slot component rendering l…
ammodev Feb 4, 2026
0ba552a
refactor: add onFirstRender logic to PaginationComponent for improved…
ammodev Feb 4, 2026
02e7c08
refactor: implement slot resolution logic for improved rendering and …
ammodev Feb 4, 2026
6737fa0
refactor: update prop handling to use mutableObjectList for improved …
ammodev Feb 4, 2026
ea4b26c
Add onInit lifecycle hook to components - called before first render
Copilot Feb 4, 2026
5b3b4f5
Revert "Add onInit lifecycle hook to components - called before first…
ammodev Feb 4, 2026
487194f
refactor: replace mutable collections with fastutil equivalents for i…
ammodev Feb 4, 2026
bda196f
refactor: comment out Hytale API modules in settings.gradle.kts
ammodev Feb 4, 2026
1d88f39
refactor: remove unused update interval logic and related jobs from G…
ammodev Feb 4, 2026
0cb3e41
Merge branch 'version/1.21.11' into copilot/implement-new-gui-framework
twisti-dev Feb 4, 2026
43e5ce3
chore: update ABI and bump version [skip ci]
github-actions[bot] Feb 4, 2026
3a5ae58
Merge remote-tracking branch 'origin/copilot/implement-new-gui-framew…
ammodev Feb 4, 2026
372f330
feat: implement component initialization logic and update imports for…
twisti-dev Feb 4, 2026
c1daf1e
refactor: remove unused launch and entityDispatcher methods from Surf…
twisti-dev Feb 4, 2026
b75d213
feat: implement new GUI framework with improved view management and i…
twisti-dev Feb 4, 2026
0857823
feat: update GUI framework with improved slot resolution and view man…
twisti-dev Feb 4, 2026
dbb9922
feat: add cancelOnClick property to components and update GUI handlin…
ammodev Feb 4, 2026
d9adef4
feat: enhance CounterGuiView with dynamic title color cycling and ref…
ammodev Feb 4, 2026
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
366 changes: 366 additions & 0 deletions docs/GUI_FRAMEWORK.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,366 @@
# SURF-API GUI Framework

A modern, React-inspired GUI framework for Minecraft servers, providing component-based architecture with reactive props and lifecycle management.

## Overview

The SURF-API GUI framework offers:

- **Component-Based Architecture**: Build GUIs using reusable, composable components
- **Reactive Props**: Manage state with mutable, immutable, computed, and lazy props
- **React-Like Lifecycle**: Components support `onMount`, `onUnmount`, `onUpdate` hooks
- **Refs System**: Reference and update components programmatically
- **View Navigation**: Navigate between parent/child views with state preservation
- **Update Intervals**: Automatic component updates at configurable intervals

## Core Concepts

### Props

Props are the state management system for GUIs. They come in several flavors:

#### Prop Types

- **ImmutableProp**: Static values that never change
- **MutableProp**: Dynamic values that can be updated
- **ComputedProp**: Values computed from a callback function
- **LazyProp**: Values loaded on first access
- **PaginationProp**: Built-in pagination support for lists

#### Prop Scopes

- **GLOBAL**: Shared across all viewers
- **VIEWER**: Isolated per viewer

```kotlin
val props = props {
immutable("title", "My GUI", PropScope.GLOBAL)
mutable("clickCount", 0, PropScope.VIEWER)
computed("displayText", PropScope.VIEWER) { context ->
"Clicks: ${mutableProp.get(context)}"
}
lazy("expensiveData", scope = PropScope.VIEWER) { context ->
loadExpensiveData()
}
pagination("items", pageSize = 9) {
listOf(Material.DIAMOND, Material.GOLD_INGOT, ...)
}
}
```

### Components

Components are the building blocks of GUIs. They render ItemStacks and handle user interactions.

```kotlin
val component = dynamicComponent(
renderer = { context ->
buildItem(ItemType.DIAMOND) {
displayName { text("Click me!") }
}
}
) {
onClick = {
player.sendMessage("You clicked!")
update() // Re-render this component
}

onMount = {
// Called when component is added to view
}

onUpdate = {
// Called when component updates
}

updateInterval = 5.seconds // Auto-update every 5 seconds
}
```

### Views

Views are the containers that manage components and handle GUI lifecycle.

```kotlin
object MyGuiView : BukkitGuiView() {
override fun onInit(config: ViewConfig) {
config.title = text("My GUI")
config.size = 54 // 6 rows
config.cancelOnClick = true
}

override fun onFirstRender(context: RenderContext) {
// Place components in slots
context.slot(0, myComponent)
context.slot(1, anotherComponent)
}

override fun onOpen(context: ViewContext) {
// Called when GUI opens for a player
}

override fun onClose(context: ViewContext) {
// Called when GUI closes
}

override fun onUpdate(context: ViewContext) {
// Called when view updates
}

override fun onResume(context: ResumeContext) {
// Called when returning from child view
val origin = context.origin // The view we came from
}
}
```

### Refs

Refs allow components to reference and update other components, enabling component communication.

```kotlin
val counterRef = createRef<Component>()
val clickCountProp = MutableProp("count", 0, PropScope.VIEWER)

// Counter display component
val counterDisplay = dynamicComponent(
renderer = { context ->
val count = clickCountProp.get(context.propContext)
buildItem(ItemType.GOLD_BLOCK) {
displayName { text("Count: $count") }
}
}
) {
ref = counterRef // Attach ref
}

// Button that updates the counter
val incrementButton = component(
item = buildItem(ItemType.EMERALD) {
displayName { text("Increment") }
}
) {
onClick = {
val current = clickCountProp.get(propContext)
clickCountProp.set(propContext, current + 1)
counterRef.update() // Update the counter display
}
}
```

### Context

All user actions and lifecycle events receive a context object with:

- **player**: The player interacting with the GUI
- **view**: The current view
- **propContext**: For accessing props
- **Navigation methods**: `navigateTo()`, `navigateBack()`, `close()`
- **update()**: Re-render the view

```kotlin
onClick = { context ->
val count = context.getProp(myProp)
context.player.sendMessage("Count: $count")
context.navigateTo(childView)
context.update()
}
```

## Navigation

Views can have parent-child relationships for navigation:

```kotlin
object ParentView : BukkitGuiView() {
override fun onFirstRender(context: RenderContext) {
context.slot(0, component(
item = buildItem(ItemType.COMPASS) {
displayName { text("Go to Child") }
}
) {
onClick = {
navigateTo(ChildView, passProps = true)
}
})
}
}

object ChildView : BukkitGuiView() {
override fun onFirstRender(context: RenderContext) {
context.slot(0, component(
item = buildItem(ItemType.ARROW) {
displayName { text("Back") }
}
) {
onClick = {
navigateBack() // Returns to ParentView
}
})
}
}
```

## Lifecycle

Components and views follow a React-like lifecycle:

### Component Lifecycle
1. **onMount**: Component added to view
2. **render**: Component rendered to ItemStack
3. **onUpdate**: Component updated (manual or interval)
4. **onClick**: User clicks component
5. **onUnmount**: Component removed from view

### View Lifecycle
1. **onInit**: View configuration initialized (once)
2. **onOpen**: View opened for player
3. **onFirstRender**: First render for player
4. **onUpdate**: View updated
5. **onResume**: Returning from child view
6. **onClose**: View closed

## Complete Example

```kotlin
object ShopView : BukkitGuiView() {
private val propsMap = props {
mutable("coins", 100, PropScope.VIEWER)
pagination("items", pageSize = 9) {
listOf(Material.DIAMOND, Material.GOLD_INGOT, Material.IRON_INGOT)
}
}

private val coinsProp = propsMap["coins"] as MutableProp<Int>
private val itemsProp = propsMap["items"] as PaginationProp<Material>
private val coinsRef = createRef<Component>()

override fun onInit(config: ViewConfig) {
config.title = text("Shop", NamedTextColor.GOLD)
config.size = 54
}

override fun onFirstRender(context: RenderContext) {
// Coins display
val coinsDisplay = dynamicComponent(
renderer = { ctx ->
val coins = coinsProp.get(ctx.propContext)
buildItem(ItemType.GOLD_INGOT) {
displayName { text("Coins: $coins", NamedTextColor.YELLOW) }
}
}
) {
ref = coinsRef
}
context.slot(4, coinsDisplay)

// Items
val pagination = itemsProp.get(context.propContext)
pagination.items.forEachIndexed { index, material ->
context.slot(9 + index, component(
item = ItemStack.of(material)
) {
onClick = {
val coins = coinsProp.get(propContext)
if (coins >= 10) {
coinsProp.set(propContext, coins - 10)
player.inventory.addItem(ItemStack.of(material))
coinsRef.update()
}
}
})
}

// Navigation buttons
if (pagination.hasPreviousPage) {
context.slot(45, component(
item = buildItem(ItemType.ARROW) {
displayName { text("Previous") }
}
) {
onClick = {
itemsProp.previousPage(propContext)
update()
}
})
}

if (pagination.hasNextPage) {
context.slot(53, component(
item = buildItem(ItemType.ARROW) {
displayName { text("Next") }
}
) {
onClick = {
itemsProp.nextPage(propContext)
update()
}
})
}
}
}

// Open the view
fun openShop(player: Player) {
ShopView.open(player)
}
```

## Opening Views

```kotlin
// Open a view for a player
MyGuiView.open(player)

// In a command
class OpenGuiCommand : CommandAPICommand("opengui") {
init {
playerExecutor { player, _ ->
MyGuiView.open(player)
}
}
}
```

## Best Practices

1. **Use Props for State**: Don't store state in variables; use props for proper viewer isolation
2. **Refs for Communication**: Use refs when components need to update each other
3. **Computed Props for Derived State**: Use computed props instead of storing derived values
4. **Lifecycle Hooks**: Use lifecycle hooks for initialization and cleanup
5. **Update Intervals**: Use sparingly; prefer manual updates via refs
6. **Navigation**: Use parent-child relationships for related views

## Migration from Old Framework

The new framework replaces the old inventory-framework-based system. Key differences:

### Old Way (inventory-framework)
```kotlin
object OldView : View() {
override fun onInit(config: ViewConfigBuilder) {
config.title("My GUI")
}

override fun onFirstRender(render: RenderContext) {
render.slot(0, item).onClick { /* ... */ }
}
}
```

### New Way (SURF-API GUI)
```kotlin
object NewView : BukkitGuiView() {
override fun onInit(config: ViewConfig) {
config.title = text("My GUI")
}

override fun onFirstRender(context: RenderContext) {
context.slot(0, component(item) {
onClick = { /* ... */ }
})
}
}
```

The new framework provides more control, better type safety, and React-like patterns familiar to modern developers.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
javaVersion=25
mcVersion=1.21.11
group=dev.slne.surf
version=1.21.11-2.56.0
version=1.21.11-2.57.0
relocationPrefix=dev.slne.surf.surfapi.libs
snapshot=false
4 changes: 2 additions & 2 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ include(":surf-api-core:surf-api-core-server")
include(":surf-api-bukkit:surf-api-bukkit-api")
include(":surf-api-bukkit:surf-api-bukkit-server")

include(":surf-api-hytale:surf-api-hytale-api")
include(":surf-api-hytale:surf-api-hytale-server")
//include(":surf-api-hytale:surf-api-hytale-api")
//include(":surf-api-hytale:surf-api-hytale-server")

include(":surf-api-velocity:surf-api-velocity-api")
include(":surf-api-velocity:surf-api-velocity-server")
Expand Down
1 change: 0 additions & 1 deletion surf-api-bukkit/surf-api-bukkit-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ dependencies {
compileOnlyApi(libs.reflection.remapper)
compileOnlyApi(libs.more.persistent.data.types)
compileOnlyApi(libs.stefvanschie.`if`)
api(libs.bundles.inventory.framework)

api(libs.commandapi.bukkit.kotlin)
compileOnlyApi(libs.mccoroutine.folia.api)
Expand Down
Loading