From 70c8678c60bf62290fbf94b8288bb9f0556e7f5e Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Fri, 12 Jun 2026 12:00:04 +0200 Subject: [PATCH] Refactor rendering system: Introduced `RenderObject` abstraction, replaced `RenderBoundary` with `ObserverListener`, and updated slot calculation for inventory types. Signed-off-by: Chaoscaot --- .../src/de/steamwar/kotlin/ui/Observer.kt | 49 +++++++++++++------ ...{RenderBoundary.kt => ObserverListener.kt} | 6 +-- .../src/de/steamwar/kotlin/ui/RenderObject.kt | 26 ++++++++++ .../src/de/steamwar/kotlin/ui/UIInventory.kt | 4 +- .../src/de/steamwar/kotlin/ui/UIWindow.kt | 3 +- .../kotlin/ui/components/ItemContext.kt | 19 +++++-- .../kotlin/ui/context/GroupContext.kt | 9 ++-- .../kotlin/ui/context/WindowContext.kt | 17 +++++-- 8 files changed, 100 insertions(+), 33 deletions(-) rename KotlinCore/src/de/steamwar/kotlin/ui/{RenderBoundary.kt => ObserverListener.kt} (89%) create mode 100644 KotlinCore/src/de/steamwar/kotlin/ui/RenderObject.kt diff --git a/KotlinCore/src/de/steamwar/kotlin/ui/Observer.kt b/KotlinCore/src/de/steamwar/kotlin/ui/Observer.kt index a5987273..8ecb1295 100644 --- a/KotlinCore/src/de/steamwar/kotlin/ui/Observer.kt +++ b/KotlinCore/src/de/steamwar/kotlin/ui/Observer.kt @@ -19,16 +19,46 @@ package de.steamwar.kotlin.ui +import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty -class Observer(private var value: T): ReadWriteProperty { - private val listeners = mutableSetOf() +abstract class Observable { + private val listeners = mutableSetOf() - fun removeListener(render: RenderBoundary) { + fun removeListener(render: ObserverListener) { listeners.remove(render) } + internal fun notifyListeners() { + listeners.forEach { it.update() } + } + + context(render: ObserverListener) + fun listen(): Observable { + listeners.add(render) + render.observers.add(this) + return this + } + + fun listen(callback: (T) -> Unit): () -> Unit { + val listener = object: ObserverListener() { + override fun update() = callback(get()) + } + listeners.add(listener) + return { removeListener(listener) } + } + + abstract fun get(): T + + fun map(mapper: (T) -> R): ReadOnlyProperty = Delegate(mapper(get())) + + class Delegate(val value: T): ReadOnlyProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): T = value + } +} + +class Observer(private var value: T): ReadWriteProperty, Observable() { override fun getValue(thisRef: Any?, property: KProperty<*>): T { return value } @@ -43,21 +73,10 @@ class Observer(private var value: T): ReadWriteProperty { notifyListeners() } - fun get() = value + override fun get() = value fun update(update: (T) -> T) { this.value = update(value) notifyListeners() } - - private fun notifyListeners() { - listeners.forEach { it.render() } - } - - context(render: RenderBoundary) - fun listen(): Observer { - listeners.add(render) - render.observers.add(this) - return this - } } diff --git a/KotlinCore/src/de/steamwar/kotlin/ui/RenderBoundary.kt b/KotlinCore/src/de/steamwar/kotlin/ui/ObserverListener.kt similarity index 89% rename from KotlinCore/src/de/steamwar/kotlin/ui/RenderBoundary.kt rename to KotlinCore/src/de/steamwar/kotlin/ui/ObserverListener.kt index 04b0b93d..d407f08c 100644 --- a/KotlinCore/src/de/steamwar/kotlin/ui/RenderBoundary.kt +++ b/KotlinCore/src/de/steamwar/kotlin/ui/ObserverListener.kt @@ -19,10 +19,10 @@ package de.steamwar.kotlin.ui -abstract class RenderBoundary { - val observers = mutableSetOf>() +abstract class ObserverListener { + val observers = mutableSetOf>() - abstract fun render() + abstract fun update() open fun destroy() { observers.forEach { it.removeListener(this) } diff --git a/KotlinCore/src/de/steamwar/kotlin/ui/RenderObject.kt b/KotlinCore/src/de/steamwar/kotlin/ui/RenderObject.kt new file mode 100644 index 00000000..3fe4d059 --- /dev/null +++ b/KotlinCore/src/de/steamwar/kotlin/ui/RenderObject.kt @@ -0,0 +1,26 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.kotlin.ui + +interface RenderObject { + fun destroy() + + fun render() +} \ No newline at end of file diff --git a/KotlinCore/src/de/steamwar/kotlin/ui/UIInventory.kt b/KotlinCore/src/de/steamwar/kotlin/ui/UIInventory.kt index ba7493ed..c9412434 100644 --- a/KotlinCore/src/de/steamwar/kotlin/ui/UIInventory.kt +++ b/KotlinCore/src/de/steamwar/kotlin/ui/UIInventory.kt @@ -23,7 +23,7 @@ import de.steamwar.kotlin.ui.context.WindowContext import org.bukkit.entity.Player import org.bukkit.event.inventory.InventoryType -abstract class UIInventory(val player: Player): RenderBoundary() { +abstract class UIInventory(val player: Player): ObserverListener() { var window: UIWindow? = null abstract fun view() @@ -37,7 +37,7 @@ abstract class UIInventory(val player: Player): RenderBoundary() { window!!.open() } - override fun render() { + fun render() { window?.onClose() window = null open() diff --git a/KotlinCore/src/de/steamwar/kotlin/ui/UIWindow.kt b/KotlinCore/src/de/steamwar/kotlin/ui/UIWindow.kt index 87eead32..e4e5cf51 100644 --- a/KotlinCore/src/de/steamwar/kotlin/ui/UIWindow.kt +++ b/KotlinCore/src/de/steamwar/kotlin/ui/UIWindow.kt @@ -46,7 +46,8 @@ class UIWindow(val player: Player, val render: WindowContext.() -> Unit): Invent } constructor(type: InventoryType, title: String, player: Player, render: WindowContext.() -> Unit): this(player, render) { - bukkitInv = KotlinCore.plugin.server.createInventory(this, type, Component.translatable(title)); + assert(type != InventoryType.CHEST) { "Chest inventories should use the constructor with size" } + bukkitInv = KotlinCore.plugin.server.createInventory(this, type, Component.translatable(title)) } fun open() { diff --git a/KotlinCore/src/de/steamwar/kotlin/ui/components/ItemContext.kt b/KotlinCore/src/de/steamwar/kotlin/ui/components/ItemContext.kt index d9730802..3609e979 100644 --- a/KotlinCore/src/de/steamwar/kotlin/ui/components/ItemContext.kt +++ b/KotlinCore/src/de/steamwar/kotlin/ui/components/ItemContext.kt @@ -19,8 +19,9 @@ package de.steamwar.kotlin.ui.components -import de.steamwar.kotlin.ui.RenderBoundary +import de.steamwar.kotlin.ui.ObserverListener import de.steamwar.kotlin.ui.RenderMarker +import de.steamwar.kotlin.ui.RenderObject import de.steamwar.kotlin.ui.context.GroupContext import de.steamwar.kotlin.ui.context.RenderParent import org.bukkit.Material @@ -28,17 +29,25 @@ import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.inventory.ItemStack @RenderMarker -class ItemContext(val parent: RenderParent, val renderFunc: ItemContext.() -> Unit): RenderBoundary() { - override fun render() { +class ItemContext(val parent: RenderParent, val renderFunc: ItemContext.() -> Unit): RenderObject, ObserverListener() { + override fun update() { val oldX = x val oldY = y - renderFunc(this) - parent.renderItem(x, y, item, onClick) + render() if (oldX != x || oldY != y) { parent.resetSlot(oldX, oldY) } } + override fun destroy() { + parent.resetSlot(x, y) + } + + override fun render() { + renderFunc(this) + parent.renderItem(x, y, item, onClick) + } + init { render() } diff --git a/KotlinCore/src/de/steamwar/kotlin/ui/context/GroupContext.kt b/KotlinCore/src/de/steamwar/kotlin/ui/context/GroupContext.kt index b0f77d86..f94d70e0 100644 --- a/KotlinCore/src/de/steamwar/kotlin/ui/context/GroupContext.kt +++ b/KotlinCore/src/de/steamwar/kotlin/ui/context/GroupContext.kt @@ -19,16 +19,19 @@ package de.steamwar.kotlin.ui.context -import de.steamwar.kotlin.ui.RenderBoundary +import de.steamwar.kotlin.ui.ObserverListener import de.steamwar.kotlin.ui.RenderMarker +import de.steamwar.kotlin.ui.RenderObject import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.inventory.ItemStack @RenderMarker -open class GroupContext(val parent: RenderParent?, val init: GroupContext.() -> Unit): RenderBoundary(), RenderParent { - val children = mutableListOf() +open class GroupContext(val parent: RenderParent?, val init: GroupContext.() -> Unit): ObserverListener(), RenderObject, RenderParent { + val children = mutableListOf() val updatedSlots = mutableSetOf>() + override fun update() = render() + override fun destroy() { children.forEach { it.destroy() } super.destroy() diff --git a/KotlinCore/src/de/steamwar/kotlin/ui/context/WindowContext.kt b/KotlinCore/src/de/steamwar/kotlin/ui/context/WindowContext.kt index fa13e683..e191d70d 100644 --- a/KotlinCore/src/de/steamwar/kotlin/ui/context/WindowContext.kt +++ b/KotlinCore/src/de/steamwar/kotlin/ui/context/WindowContext.kt @@ -23,18 +23,27 @@ import de.steamwar.kotlin.ui.RenderMarker import de.steamwar.kotlin.ui.UIWindow import org.bukkit.Material import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.event.inventory.InventoryType import org.bukkit.inventory.ItemStack @RenderMarker class WindowContext(val window: UIWindow): GroupContext(null, {}) { override fun renderItem(x: Int, y: Int, item: ItemStack, onClick: (event: InventoryClickEvent) -> Unit) { - window.bukkitInv.setItem(x + y * 9, item) - window.onClicks[x + y * 9] = onClick + val slot = calculateSlot(x, y) + window.bukkitInv.setItem(slot, item) + window.onClicks[slot] = onClick } override fun resetSlot(x: Int, y: Int) { - window.bukkitInv.setItem(x + y * 9, ItemStack.of(Material.AIR)) - window.onClicks.remove(x + y * 9) + val slot = calculateSlot(x, y) + window.bukkitInv.setItem(slot, ItemStack.of(Material.AIR)) + window.onClicks.remove(slot) + } + + private fun calculateSlot(x: Int, y: Int) = when (window.inventory.type) { + InventoryType.DROPPER, InventoryType.DISPENSER -> x + y * 3 + InventoryType.HOPPER -> x + else -> x + y * 9 } fun outsideClick(click: (event: InventoryClickEvent) -> Unit) {