├── core ├── build.gradle.kts ├── README.md └── src │ └── main │ └── java │ └── org │ └── incendo │ └── interfaces │ └── core │ ├── util │ ├── package-info.java │ └── Vector2.java │ ├── click │ ├── clicks │ │ ├── package-info.java │ │ ├── UnknownClick.java │ │ ├── MiddleClick.java │ │ ├── LeftClick.java │ │ ├── RightClick.java │ │ └── Clicks.java │ ├── package-info.java │ ├── Click.java │ ├── ClickContext.java │ └── ClickHandler.java │ ├── package-info.java │ ├── arguments │ ├── package-info.java │ ├── MutableInterfaceArguments.java │ ├── ImmutableDelegatingInterfaceArguments.java │ ├── ArgumentKeyImpl.java │ ├── ArgumentKey.java │ └── InterfaceArguments.java │ ├── transform │ ├── package-info.java │ ├── types │ │ └── package-info.java │ ├── TriConsumer.java │ ├── Transform.java │ ├── ReactiveTransform.java │ ├── InterruptUpdateException.java │ ├── DummyInterfaceProperty.java │ ├── Pair.java │ ├── InterfaceProperty.java │ ├── InterfacePropertyImpl.java │ └── TransformContext.java │ ├── view │ ├── package-info.java │ ├── SelfUpdatingInterfaceView.java │ ├── InterfaceViewer.java │ └── InterfaceView.java │ ├── element │ ├── package-info.java │ └── Element.java │ ├── pane │ ├── package-info.java │ ├── Pane.java │ └── GridPane.java │ └── UpdatingInterface.java ├── .github ├── CODEOWNERS └── workflows │ └── gradle.yml ├── .gitignore ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.conf ├── next ├── src │ └── main │ │ └── kotlin │ │ └── org │ │ └── incendo │ │ └── interfaces │ │ └── next │ │ ├── pane │ │ ├── ChestPane.kt │ │ ├── OrderedPane.kt │ │ ├── CombinedPane.kt │ │ ├── Pane.kt │ │ ├── PlayerPane.kt │ │ └── CompletedPane.kt │ │ ├── grid │ │ ├── GridPositionGenerator.kt │ │ ├── GridBoxGenerator.kt │ │ ├── GridPoint.kt │ │ ├── GridMap.kt │ │ └── HashGridMap.kt │ │ ├── interfaces │ │ ├── TitledInterface.kt │ │ ├── InterfaceBuilder.kt │ │ ├── CloseHandler.kt │ │ ├── PlayerInterfaceBuilder.kt │ │ ├── InterfaceExt.kt │ │ ├── ChestInterfaceBuilder.kt │ │ ├── CombinedInterfaceBuilder.kt │ │ ├── PlayerInterface.kt │ │ ├── CombinedInterface.kt │ │ ├── ChestInterface.kt │ │ ├── Interface.kt │ │ └── AbstractInterfaceBuilder.kt │ │ ├── properties │ │ ├── InterfacePropertyExt.kt │ │ ├── Trigger.kt │ │ ├── EmptyTrigger.kt │ │ ├── InterfaceProperty.kt │ │ └── DelegateTrigger.kt │ │ ├── transform │ │ ├── Transform.kt │ │ ├── ReactiveTransform.kt │ │ ├── AppliedTransform.kt │ │ └── builtin │ │ │ ├── PagedTransformation.kt │ │ │ └── PaginationTransformation.kt │ │ ├── utilities │ │ ├── IncrementingInteger.kt │ │ ├── ThreadUtils.kt │ │ ├── TitleState.kt │ │ ├── BukkitInventoryUtilities.kt │ │ ├── CollapsablePaneMap.kt │ │ └── BoundInteger.kt │ │ ├── click │ │ ├── ClickContext.kt │ │ └── ClickHandler.kt │ │ ├── element │ │ ├── Element.kt │ │ ├── StaticElement.kt │ │ └── CompletedElement.kt │ │ ├── view │ │ ├── InterfaceView.kt │ │ ├── LockUtils.kt │ │ ├── ChestInterfaceView.kt │ │ ├── CombinedInterfaceView.kt │ │ └── PlayerInterfaceView.kt │ │ ├── inventory │ │ ├── InterfacesInventory.kt │ │ ├── CachedInterfacesInventory.kt │ │ ├── PlayerInterfacesInventory.kt │ │ ├── ChestInterfacesInventory.kt │ │ └── CombinedInterfacesInventory.kt │ │ ├── drawable │ │ └── Drawable.kt │ │ ├── event │ │ └── DrawPaneEvent.kt │ │ └── Constants.kt └── build.gradle.kts ├── examples ├── example-next │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── plugin.yml │ │ │ └── kotlin │ │ │ └── org │ │ │ └── incendo │ │ │ └── interfaces │ │ │ └── example │ │ │ └── next │ │ │ ├── RegistrableInterface.kt │ │ │ ├── ExampleUtilities.kt │ │ │ ├── CatalogueExampleInterface.kt │ │ │ ├── MovingExampleInterface.kt │ │ │ ├── ChangingTitleExampleInterface.kt │ │ │ ├── DelayedRequestExampleInterface.kt │ │ │ └── TabbedExampleInterface.kt │ └── build.gradle.kts ├── example-java │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── plugin.yml │ │ │ └── java │ │ │ └── org │ │ │ └── incendo │ │ │ └── interfaces │ │ │ └── example │ │ │ └── java │ │ │ └── ExampleJavaPlugin.java │ └── build.gradle.kts └── example-kotlin │ ├── src │ └── main │ │ ├── resources │ │ └── plugin.yml │ │ └── kotlin │ │ └── org │ │ └── incendo │ │ └── interfaces │ │ └── example │ │ └── kotlin │ │ └── SelectionOptions.kt │ └── build.gradle.kts ├── paper ├── src │ └── main │ │ └── java │ │ └── org │ │ └── incendo │ │ └── interfaces │ │ └── paper │ │ ├── package-info.java │ │ ├── click │ │ ├── package-info.java │ │ └── InventoryClickContext.java │ │ ├── utils │ │ ├── package-info.java │ │ ├── InterfacesUpdateExecutor.java │ │ ├── DefaultInterfacesUpdateExecutor.java │ │ ├── SynchronousInterfacesUpdateExecutor.java │ │ ├── EventUtil.java │ │ ├── PaperUtils.java │ │ ├── Components.java │ │ └── InventoryFactory.java │ │ ├── view │ │ ├── package-info.java │ │ ├── BukkitNestedRunnable.java │ │ ├── ContextCompletedPane.java │ │ ├── ChildView.java │ │ ├── TaskableView.java │ │ ├── ViewOpenEvent.java │ │ ├── ViewCloseEvent.java │ │ ├── PlayerView.java │ │ └── BookView.java │ │ ├── transform │ │ ├── package-info.java │ │ └── PaperTransform.java │ │ ├── pane │ │ ├── package-info.java │ │ ├── BookPane.java │ │ ├── ChestPane.java │ │ └── CombinedPane.java │ │ ├── type │ │ ├── package-info.java │ │ ├── CloseHandler.java │ │ ├── Clickable.java │ │ ├── ChildTitledInterface.java │ │ └── TitledInterface.java │ │ ├── element │ │ ├── package-info.java │ │ ├── TextElement.java │ │ └── ItemStackElement.java │ │ ├── PlayerViewer.java │ │ └── PlayerViewerImpl.java ├── build.gradle.kts └── README.md ├── .checkstyle └── checkstyle-suppressions.xml ├── kotlin ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── org │ └── incendo │ └── interfaces │ └── kotlin │ ├── InterfacePropertyExt.kt │ ├── ClickHandlerExt.kt │ ├── MutableInterfaceBuilder.kt │ ├── InterfaceViewExt.kt │ ├── Arguments.kt │ └── paper │ ├── MutableChestPaneView.kt │ ├── MutableCombinedPaneView.kt │ ├── MutablePlayerPaneView.kt │ ├── PaperExt.kt │ └── MutablePlayerInterfaceBuilder.kt ├── settings.gradle.kts └── gradlew.bat /core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @kadenscott 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | **/build 4 | **/run 5 | -------------------------------------------------------------------------------- /core/README.md: -------------------------------------------------------------------------------- 1 | # `interfaces/core` 2 | 3 | This package contains the core API of `interfaces`. 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Incendo/interfaces/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/pane/ChestPane.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.pane 2 | 3 | public class ChestPane : Pane() 4 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/util/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility classes. 3 | */ 4 | package org.incendo.interfaces.core.util; 5 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/click/clicks/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Click types. 3 | */ 4 | package org.incendo.interfaces.core.click.clicks; 5 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Main package of the Interfaces library. 3 | */ 4 | package org.incendo.interfaces.core; 5 | -------------------------------------------------------------------------------- /examples/example-next/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: ExampleNextPlugin 2 | version: 1.0.0 3 | main: org.incendo.interfaces.example.next.NextPlugin 4 | api-version: 1.16 5 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/click/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Classes related to Paper click handling. 3 | */ 4 | package org.incendo.interfaces.core.click; 5 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Paper implementation of the Interfaces library. 3 | */ 4 | package org.incendo.interfaces.paper; 5 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/arguments/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Classes related to Interface arguments. 3 | */ 4 | package org.incendo.interfaces.core.arguments; 5 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/transform/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Classes related to transforming a Pane. 3 | */ 4 | package org.incendo.interfaces.core.transform; 5 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/transform/types/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Pre-made transform types. 3 | */ 4 | package org.incendo.interfaces.core.transform.types; 5 | -------------------------------------------------------------------------------- /examples/example-java/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: ExampleJavaPlugin 2 | version: 1.0.0 3 | main: org.incendo.interfaces.example.java.ExampleJavaPlugin 4 | api-version: 1.16 5 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/click/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Classes related to Paper click handling. 3 | */ 4 | package org.incendo.interfaces.paper.click; 5 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/utils/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Utilities for the Paper interfaces implementation. 3 | */ 4 | package org.incendo.interfaces.paper.utils; 5 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/view/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Available Paper implementations of an interface view. 3 | */ 4 | package org.incendo.interfaces.paper.view; 5 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/transform/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Pre-made interface transformations for Paper interfaces. 3 | */ 4 | package org.incendo.interfaces.paper.transform; 5 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/pane/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Paper implementations of {@link org.incendo.interfaces.core.pane.Pane}. 3 | */ 4 | package org.incendo.interfaces.paper.pane; 5 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/view/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Classes related to encapsulating a View of an interface as well as viewers. 3 | */ 4 | package org.incendo.interfaces.core.view; 5 | -------------------------------------------------------------------------------- /examples/example-java/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.runPaper) 3 | } 4 | 5 | dependencies { 6 | implementation(projects.interfacesPaper) 7 | 8 | compileOnly(libs.paper.api) 9 | } 10 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/type/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Available Paper implementations of an {@link org.incendo.interfaces.core.Interface} 3 | */ 4 | package org.incendo.interfaces.paper.type; 5 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/element/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Classes related to interface elements. 3 | * These are distinct items on an interface. 4 | */ 5 | package org.incendo.interfaces.core.element; 6 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/pane/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Classes related to interface panes. 3 | * These are a collection of elements on an interface. 4 | */ 5 | package org.incendo.interfaces.core.pane; 6 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/grid/GridPositionGenerator.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.grid 2 | 3 | public fun interface GridPositionGenerator { 4 | public fun generate(): List 5 | } 6 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/element/Element.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.element; 2 | 3 | /** 4 | * Represents an element that can be rendered to a pane. 5 | */ 6 | public interface Element { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/element/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Classes related to Paper implementations of {@link org.incendo.interfaces.core.element.Element}. 3 | */ 4 | package org.incendo.interfaces.paper.element; 5 | -------------------------------------------------------------------------------- /examples/example-kotlin/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: ExampleKotlinPlugin 2 | version: 1.0.0 3 | main: org.incendo.interfaces.example.kotlin.KotlinPlugin 4 | api-version: 1.16 5 | commands: 6 | interfaces: 7 | description: Main interface command 8 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/interfaces/TitledInterface.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.interfaces 2 | 3 | import net.kyori.adventure.text.Component 4 | 5 | public interface TitledInterface { 6 | public val initialTitle: Component? 7 | } 8 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/properties/InterfacePropertyExt.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.properties 2 | 3 | public fun interfaceProperty(value: T): InterfaceProperty = InterfaceProperty(value) 4 | 5 | public fun emptyTrigger(): Trigger = EmptyTrigger 6 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/interfaces/InterfaceBuilder.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.interfaces 2 | 3 | import org.incendo.interfaces.next.pane.Pane 4 | 5 | public abstract class InterfaceBuilder

> { 6 | public abstract fun build(): T 7 | } 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/properties/Trigger.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.properties 2 | 3 | public interface Trigger { 4 | public fun trigger() 5 | 6 | public fun addListener( 7 | reference: T, 8 | listener: T.() -> Unit, 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/transform/Transform.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.transform 2 | 3 | import org.incendo.interfaces.next.pane.Pane 4 | import org.incendo.interfaces.next.view.InterfaceView 5 | 6 | public fun interface Transform

: suspend (P, InterfaceView) -> Unit 7 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/interfaces/CloseHandler.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.interfaces 2 | 3 | import org.bukkit.event.inventory.InventoryCloseEvent 4 | import org.incendo.interfaces.next.view.InterfaceView 5 | 6 | public fun interface CloseHandler : suspend (InventoryCloseEvent.Reason, InterfaceView) -> Unit 7 | -------------------------------------------------------------------------------- /examples/example-next/src/main/kotlin/org/incendo/interfaces/example/next/RegistrableInterface.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.example.next 2 | 3 | import org.incendo.interfaces.next.interfaces.Interface 4 | 5 | public interface RegistrableInterface { 6 | public val subcommand: String 7 | 8 | public fun create(): Interface<*> 9 | } 10 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/transform/ReactiveTransform.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.transform 2 | 3 | import org.incendo.interfaces.next.pane.Pane 4 | import org.incendo.interfaces.next.properties.Trigger 5 | 6 | public interface ReactiveTransform

: Transform

{ 7 | public val triggers: Array 8 | } 9 | -------------------------------------------------------------------------------- /.checkstyle/checkstyle-suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /paper/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(projects.interfacesCore) 3 | 4 | compileOnly(libs.adventure.api) 5 | compileOnly("net.kyori:adventure-text-serializer-gson:4.11.0") 6 | compileOnly("net.kyori:adventure-text-serializer-legacy:4.11.0") 7 | compileOnly(libs.paper.api) { 8 | isTransitive = false 9 | } 10 | compileOnly(libs.guava) 11 | } 12 | -------------------------------------------------------------------------------- /examples/example-java/src/main/java/org/incendo/interfaces/example/java/ExampleJavaPlugin.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.example.java; 2 | 3 | import org.bukkit.plugin.java.JavaPlugin; 4 | 5 | /** 6 | * The example plugin's main class. 7 | */ 8 | public class ExampleJavaPlugin extends JavaPlugin { 9 | 10 | @Override 11 | public void onEnable() { 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /paper/README.md: -------------------------------------------------------------------------------- 1 | # `interfaces/paper` 2 | 3 | This package contains the [Paper Minecraft server](https://papermc.io) implementation. 4 | 5 | Please note: to make sure `interface` click handlers work properly, ensure you register the [PaperInterfaceListeners](https://github.com/Incendo/interfaces/blob/master/paper/src/main/java/dev/kscott/interfaces/paper/PaperInterfaceListeners.java) listener. 6 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/utilities/IncrementingInteger.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.utilities 2 | 3 | import kotlin.reflect.KProperty 4 | 5 | internal class IncrementingInteger { 6 | private var value: Int = 0 7 | get() = field++ 8 | 9 | operator fun getValue( 10 | thisRef: Any?, 11 | property: KProperty<*>, 12 | ): Int = value 13 | } 14 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/click/ClickContext.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.click 2 | 3 | import org.bukkit.entity.Player 4 | import org.bukkit.event.inventory.ClickType 5 | import org.incendo.interfaces.next.view.InterfaceView 6 | 7 | public data class ClickContext( 8 | public val player: Player, 9 | public val view: InterfaceView, 10 | public val type: ClickType, 11 | ) 12 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/properties/EmptyTrigger.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.properties 2 | 3 | public object EmptyTrigger : Trigger { 4 | override fun trigger() { 5 | // no implementation 6 | } 7 | 8 | override fun addListener( 9 | reference: T, 10 | listener: T.() -> Unit, 11 | ) { 12 | // no implementation 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/transform/AppliedTransform.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.transform 2 | 3 | import org.incendo.interfaces.next.pane.Pane 4 | import org.incendo.interfaces.next.properties.Trigger 5 | 6 | public class AppliedTransform

( 7 | internal val priority: Int, 8 | internal val triggers: Set, 9 | transform: Transform

, 10 | ) : Transform

by transform 11 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/transform/TriConsumer.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.transform; 2 | 3 | @FunctionalInterface 4 | public interface TriConsumer { 5 | 6 | /** 7 | * Accepts three values [t], [u] and [v]. 8 | * 9 | * @param t the first value 10 | * @param u the second value 11 | * @param v the third value 12 | */ 13 | void accept(T t, U u, V v); 14 | } 15 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/transform/Transform.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.transform; 2 | 3 | import org.incendo.interfaces.core.pane.Pane; 4 | import org.incendo.interfaces.core.view.InterfaceView; 5 | import org.incendo.interfaces.core.view.InterfaceViewer; 6 | 7 | import java.util.function.BiFunction; 8 | 9 | public interface Transform extends BiFunction, T> { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/grid/GridBoxGenerator.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.grid 2 | 3 | /** 4 | * A grid position generator that generates every point inside a box with corners of 5 | * [min] and [max] (inclusive). 6 | */ 7 | public data class GridBoxGenerator( 8 | private val min: GridPoint, 9 | private val max: GridPoint, 10 | ) : GridPositionGenerator { 11 | override fun generate(): List = (min..max).toList() 12 | } 13 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/view/SelfUpdatingInterfaceView.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.view; 2 | 3 | /** 4 | * Represents an Interface View that does not need to be reopened to be refreshed 5 | */ 6 | public interface SelfUpdatingInterfaceView { 7 | 8 | /** 9 | * Returns a boolean; true if updating, false if not 10 | * 11 | * @return true if updating interface, false if not 12 | */ 13 | boolean updates(); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/utils/InterfacesUpdateExecutor.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.utils; 2 | 3 | import org.bukkit.plugin.Plugin; 4 | 5 | public interface InterfacesUpdateExecutor { 6 | 7 | /** 8 | * Run the given task via the executor 9 | * 10 | * @param plugin the plugin instance of the interface 11 | * @param runnable the runnable to execute 12 | */ 13 | void execute(Plugin plugin, Runnable runnable); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/element/Element.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.element 2 | 3 | import org.bukkit.Material 4 | import org.incendo.interfaces.next.click.ClickHandler 5 | import org.incendo.interfaces.next.drawable.Drawable 6 | 7 | public interface Element { 8 | public companion object EMPTY : Element by StaticElement(Drawable.drawable(Material.AIR)) 9 | 10 | public fun drawable(): Drawable 11 | 12 | public fun clickHandler(): ClickHandler 13 | } 14 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/utilities/ThreadUtils.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.utilities 2 | 3 | import org.bukkit.Bukkit 4 | import org.bukkit.plugin.java.PluginClassLoader 5 | 6 | internal fun runSync(function: () -> Unit) { 7 | if (Bukkit.isPrimaryThread()) { 8 | function() 9 | return 10 | } 11 | 12 | val plugin = (function::class.java.classLoader as PluginClassLoader).plugin 13 | Bukkit.getScheduler().callSyncMethod(plugin!!, function) 14 | } 15 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/utils/DefaultInterfacesUpdateExecutor.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.utils; 2 | 3 | import org.bukkit.plugin.Plugin; 4 | 5 | /** 6 | * Runs the update on whatever thread the update was called from. 7 | */ 8 | public final class DefaultInterfacesUpdateExecutor implements InterfacesUpdateExecutor { 9 | 10 | @Override 11 | public void execute(final Plugin plugin, final Runnable runnable) { 12 | runnable.run(); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/interfaces/PlayerInterfaceBuilder.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.interfaces 2 | 3 | import org.incendo.interfaces.next.pane.PlayerPane 4 | 5 | public class PlayerInterfaceBuilder : AbstractInterfaceBuilder() { 6 | override fun build(): PlayerInterface = 7 | PlayerInterface( 8 | closeHandlers, 9 | transforms, 10 | clickPreprocessors, 11 | itemPostProcessor, 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/element/StaticElement.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.element 2 | 3 | import org.incendo.interfaces.next.click.ClickHandler 4 | import org.incendo.interfaces.next.drawable.Drawable 5 | 6 | public class StaticElement public constructor( 7 | private val drawable: Drawable, 8 | private val clickHandler: ClickHandler = ClickHandler.EMPTY, 9 | ) : Element { 10 | override fun drawable(): Drawable = drawable 11 | 12 | override fun clickHandler(): ClickHandler = clickHandler 13 | } 14 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/view/InterfaceView.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.view 2 | 3 | import net.kyori.adventure.text.Component 4 | 5 | public interface InterfaceView { 6 | public suspend fun open() 7 | 8 | public fun close() 9 | 10 | // todo(josh): temporarily done for interfaces 1 shim. 11 | // should we keep it? 12 | public fun parent(): InterfaceView? 13 | 14 | public suspend fun back() 15 | 16 | public fun title(value: Component) 17 | 18 | public fun onOpen() 19 | } 20 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/UpdatingInterface.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core; 2 | 3 | /** 4 | * Represents an interface that can update. 5 | */ 6 | public interface UpdatingInterface { 7 | 8 | /** 9 | * Returns a boolean; true if updating, false if not 10 | * 11 | * @return true if updating interface, false if not 12 | */ 13 | boolean updates(); 14 | 15 | /** 16 | * Returns the update delay. 17 | * 18 | * @return the update delay 19 | */ 20 | int updateDelay(); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/inventory/InterfacesInventory.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.inventory 2 | 3 | import org.bukkit.inventory.ItemStack 4 | 5 | public interface InterfacesInventory { 6 | public fun isPlayerInventory( 7 | row: Int, 8 | column: Int, 9 | ): Boolean 10 | 11 | public fun set( 12 | row: Int, 13 | column: Int, 14 | item: ItemStack?, 15 | ): Boolean 16 | 17 | public fun get( 18 | row: Int, 19 | column: Int, 20 | ): ItemStack? 21 | } 22 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/drawable/Drawable.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.drawable 2 | 3 | import org.bukkit.Material 4 | import org.bukkit.entity.Player 5 | import org.bukkit.inventory.ItemStack 6 | 7 | public fun interface Drawable { 8 | public companion object { 9 | public fun drawable(item: ItemStack): Drawable = Drawable { item } 10 | 11 | public fun drawable(material: Material): Drawable = Drawable { ItemStack(material) } 12 | } 13 | 14 | public suspend fun draw(player: Player): ItemStack 15 | } 16 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/element/CompletedElement.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.element 2 | 3 | import org.bukkit.entity.Player 4 | import org.bukkit.inventory.ItemStack 5 | import org.incendo.interfaces.next.click.ClickHandler 6 | 7 | internal data class CompletedElement( 8 | public val itemStack: ItemStack?, 9 | public val clickHandler: ClickHandler, 10 | ) 11 | 12 | internal suspend fun Element.complete(player: Player) = 13 | CompletedElement( 14 | drawable().draw(player), 15 | clickHandler(), 16 | ) 17 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/type/CloseHandler.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.type; 2 | 3 | import org.bukkit.event.inventory.InventoryCloseEvent; 4 | import org.incendo.interfaces.core.pane.Pane; 5 | import org.incendo.interfaces.paper.view.PlayerView; 6 | 7 | import java.util.function.BiConsumer; 8 | 9 | /** 10 | * A function that handles a close event on an interface. 11 | * 12 | * @param the pane type 13 | */ 14 | public interface CloseHandler extends BiConsumer> { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/properties/InterfaceProperty.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.properties 2 | 3 | import kotlin.properties.ObservableProperty 4 | import kotlin.reflect.KProperty 5 | 6 | public class InterfaceProperty( 7 | defaultValue: T, 8 | ) : ObservableProperty(defaultValue), 9 | Trigger by DelegateTrigger() { 10 | override fun afterChange( 11 | property: KProperty<*>, 12 | oldValue: T, 13 | newValue: T, 14 | ) { 15 | if (oldValue != newValue) { 16 | trigger() 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/view/InterfaceViewer.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.view; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | 5 | /** 6 | * Represents a target that can view an interface. 7 | */ 8 | @SuppressWarnings("unused") 9 | public interface InterfaceViewer { 10 | 11 | /** 12 | * Displays a view to the viewer. 13 | * 14 | * @param pane the pane 15 | */ 16 | void open(@NonNull InterfaceView pane); 17 | 18 | /** 19 | * Closes the open view, if any. 20 | */ 21 | void close(); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/event/DrawPaneEvent.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.event 2 | 3 | import org.bukkit.entity.Player 4 | import org.bukkit.event.HandlerList 5 | import org.bukkit.event.player.PlayerEvent 6 | 7 | /** An event emitted when the inventory of [player] is drawn. */ 8 | public class DrawPaneEvent( 9 | player: Player, 10 | ) : PlayerEvent(player) { 11 | public companion object { 12 | @JvmStatic 13 | public val handlerList: HandlerList = HandlerList() 14 | } 15 | 16 | override fun getHandlers(): HandlerList = handlerList 17 | } 18 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/interfaces/InterfaceExt.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.interfaces 2 | 3 | public inline fun buildChestInterface(builder: ChestInterfaceBuilder.() -> Unit): ChestInterface = 4 | ChestInterfaceBuilder().also(builder).build() 5 | 6 | public inline fun buildPlayerInterface(builder: PlayerInterfaceBuilder.() -> Unit): PlayerInterface = 7 | PlayerInterfaceBuilder().also(builder).build() 8 | 9 | public inline fun buildCombinedInterface(builder: CombinedInterfaceBuilder.() -> Unit): CombinedInterface = 10 | CombinedInterfaceBuilder().also(builder).build() 11 | -------------------------------------------------------------------------------- /examples/example-kotlin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | alias(libs.plugins.dokka) 4 | alias(libs.plugins.shadow) 5 | alias(libs.plugins.runPaper) 6 | alias(libs.plugins.ktlint) 7 | } 8 | 9 | tasks { 10 | compileKotlin { 11 | kotlinOptions.jvmTarget = "11" 12 | } 13 | 14 | compileTestKotlin { 15 | kotlinOptions.jvmTarget = "11" 16 | } 17 | } 18 | 19 | kotlin { 20 | explicitApi() 21 | } 22 | 23 | dependencies { 24 | implementation(projects.interfacesPaper) 25 | implementation(projects.interfacesKotlin) 26 | 27 | compileOnly(libs.paper.api) 28 | } 29 | -------------------------------------------------------------------------------- /kotlin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 2 | 3 | plugins { 4 | kotlin("jvm") 5 | alias(libs.plugins.dokka) 6 | alias(libs.plugins.ktlint) 7 | } 8 | 9 | kotlin { 10 | compilerOptions { 11 | jvmTarget = JvmTarget.JVM_17 12 | } 13 | } 14 | 15 | ktlint { 16 | version.set("1.7.1") 17 | } 18 | 19 | kotlin { 20 | explicitApi() 21 | } 22 | 23 | dependencies { 24 | api(projects.interfacesCore) 25 | 26 | // Needed for Paper extensions. 27 | compileOnly(projects.interfacesPaper) 28 | compileOnly(libs.adventure.api) 29 | compileOnly(libs.paper.api) 30 | } 31 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/utilities/TitleState.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.utilities 2 | 3 | import net.kyori.adventure.text.Component 4 | 5 | internal class TitleState( 6 | initialState: Component?, 7 | ) { 8 | internal var current = initialState 9 | set(value) { 10 | // Don't update if nothing changed 11 | if (field == value) return 12 | 13 | hasChanged = true 14 | field = value 15 | } 16 | get() { 17 | hasChanged = false 18 | return field 19 | } 20 | 21 | internal var hasChanged = false 22 | } 23 | -------------------------------------------------------------------------------- /examples/example-next/src/main/kotlin/org/incendo/interfaces/example/next/ExampleUtilities.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.example.next 2 | 3 | import net.kyori.adventure.text.Component 4 | import org.bukkit.inventory.ItemStack 5 | 6 | public fun ItemStack.name(name: String): ItemStack { 7 | itemMeta = 8 | itemMeta.also { meta -> 9 | meta.displayName(Component.text(name)) 10 | } 11 | return this 12 | } 13 | 14 | public fun ItemStack.description(description: String): ItemStack { 15 | itemMeta = 16 | itemMeta.also { meta -> 17 | meta.lore(listOf(Component.text(description))) 18 | } 19 | return this 20 | } 21 | -------------------------------------------------------------------------------- /next/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 2 | 3 | plugins { 4 | kotlin("jvm") 5 | alias(libs.plugins.dokka) 6 | } 7 | 8 | dependencies { 9 | compileOnlyApi(libs.adventure.api) 10 | compileOnlyApi(libs.paper.api) { 11 | isTransitive = false 12 | } 13 | compileOnlyApi(libs.guava) 14 | api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") 15 | api("org.slf4j:slf4j-api:1.7.36") 16 | implementation("com.github.ben-manes.caffeine:caffeine:3.1.6") 17 | } 18 | 19 | kotlin { 20 | compilerOptions { 21 | jvmTarget = JvmTarget.JVM_17 22 | } 23 | } 24 | 25 | kotlin { 26 | explicitApi() 27 | } 28 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/interfaces/ChestInterfaceBuilder.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.interfaces 2 | 3 | import net.kyori.adventure.text.Component 4 | import org.incendo.interfaces.next.pane.ChestPane 5 | 6 | public class ChestInterfaceBuilder : AbstractInterfaceBuilder() { 7 | public var rows: Int = 0 8 | public var initialTitle: Component? = null 9 | 10 | override fun build(): ChestInterface = 11 | ChestInterface( 12 | rows, 13 | initialTitle, 14 | closeHandlers, 15 | transforms, 16 | clickPreprocessors, 17 | itemPostProcessor, 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/interfaces/CombinedInterfaceBuilder.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.interfaces 2 | 3 | import net.kyori.adventure.text.Component 4 | import org.incendo.interfaces.next.pane.CombinedPane 5 | 6 | public class CombinedInterfaceBuilder : AbstractInterfaceBuilder() { 7 | public var rows: Int = 0 8 | public var initialTitle: Component? = null 9 | 10 | override fun build(): CombinedInterface = 11 | CombinedInterface( 12 | rows, 13 | initialTitle, 14 | closeHandlers, 15 | transforms, 16 | clickPreprocessors, 17 | itemPostProcessor, 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/inventory/CachedInterfacesInventory.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.inventory 2 | 3 | import org.bukkit.inventory.ItemStack 4 | 5 | public abstract class CachedInterfacesInventory : InterfacesInventory { 6 | final override fun set( 7 | row: Int, 8 | column: Int, 9 | item: ItemStack?, 10 | ): Boolean { 11 | val current = get(row, column) 12 | 13 | if (current == item) { 14 | return false 15 | } 16 | 17 | setInternal(row, column, item) 18 | return true 19 | } 20 | 21 | protected abstract fun setInternal( 22 | row: Int, 23 | column: Int, 24 | item: ItemStack?, 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /examples/example-next/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 2 | 3 | plugins { 4 | kotlin("jvm") 5 | alias(libs.plugins.dokka) 6 | alias(libs.plugins.shadow) 7 | alias(libs.plugins.runPaper) 8 | } 9 | 10 | kotlin { 11 | compilerOptions { 12 | jvmTarget = JvmTarget.JVM_17 13 | } 14 | } 15 | 16 | kotlin { 17 | explicitApi() 18 | } 19 | 20 | dependencies { 21 | implementation(projects.interfacesNext) 22 | implementation("cloud.commandframework", "cloud-paper", "1.7.1") 23 | implementation("cloud.commandframework", "cloud-kotlin-extensions", "1.7.1") 24 | implementation("cloud.commandframework", "cloud-kotlin-coroutines", "1.7.1") 25 | 26 | compileOnly(libs.paper.api) 27 | } 28 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/view/LockUtils.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.view 2 | 3 | import java.util.concurrent.TimeUnit 4 | import java.util.concurrent.locks.Lock 5 | import kotlin.contracts.ExperimentalContracts 6 | import kotlin.contracts.InvocationKind 7 | import kotlin.contracts.contract 8 | 9 | @OptIn(ExperimentalContracts::class) 10 | internal inline fun Lock.withLock( 11 | time: Long, 12 | unit: TimeUnit, 13 | action: () -> T, 14 | ): T? { 15 | contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } 16 | return if (tryLock(time, unit)) { 17 | try { 18 | action() 19 | } finally { 20 | unlock() 21 | } 22 | } else { 23 | null 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /kotlin/src/main/kotlin/org/incendo/interfaces/kotlin/InterfacePropertyExt.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.kotlin 2 | 3 | import org.incendo.interfaces.core.transform.InterfaceProperty 4 | import kotlin.reflect.KProperty 5 | 6 | /** Setter delegate. */ 7 | public operator fun InterfaceProperty.setValue( 8 | thisRef: Any?, 9 | property: KProperty<*>, 10 | value: T, 11 | ) { 12 | this.set(value) 13 | } 14 | 15 | /** Getter delegate. */ 16 | public operator fun InterfaceProperty.getValue( 17 | thisRef: Any?, 18 | property: KProperty<*>, 19 | ): T = this.get() 20 | 21 | /** Getter/Setter delegate. */ 22 | public var InterfaceProperty.value: T 23 | get() = this.get() 24 | set(value) { 25 | this.set(value) 26 | } 27 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/pane/OrderedPane.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.pane 2 | 3 | import org.incendo.interfaces.next.element.Element 4 | 5 | public abstract class OrderedPane( 6 | internal val ordering: List, 7 | ) : Pane() { 8 | override fun get( 9 | row: Int, 10 | column: Int, 11 | ): Element? = super.get(orderedRow(row), column) 12 | 13 | override fun set( 14 | row: Int, 15 | column: Int, 16 | value: Element, 17 | ): Unit = super.set(orderedRow(row), column, value) 18 | 19 | override fun has( 20 | row: Int, 21 | column: Int, 22 | ): Boolean = super.has(orderedRow(row), column) 23 | 24 | private fun orderedRow(row: Int) = ordering[row] 25 | } 26 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/pane/Pane.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.pane; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.incendo.interfaces.core.element.Element; 5 | 6 | import java.util.Collection; 7 | 8 | /** 9 | * A pane represents the method of viewing the interface. A pane 10 | * is just a collection of {@link Element}s. 11 | *

12 | * For example, a pane may be text-only (using elements holding text), or a pane may be a grid of icon elements. 13 | */ 14 | public interface Pane { 15 | 16 | /** 17 | * Returns a collection containing all the elements of this pane. 18 | * 19 | * @return the element collection 20 | */ 21 | @NonNull Collection elements(); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/pane/CombinedPane.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.pane 2 | 3 | public class CombinedPane( 4 | chestRows: Int, 5 | ) : OrderedPane(createMappings(chestRows)) { 6 | private companion object { 7 | private fun createMappings(rows: Int): List = 8 | buildList { 9 | IntRange(0, rows - 1).forEach(::add) 10 | 11 | // the players hotbar is row 0 in the players inventory, 12 | // for combined interfaces it makes more sense for hotbar 13 | // to be the last row, so reshuffle here. 14 | add(rows + 1) 15 | add(rows + 2) 16 | add(rows + 3) 17 | add(rows) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("ca.stellardrift.polyglot-version-catalogs") version "6.0.1" 3 | } 4 | 5 | rootProject.name = "interfaces" 6 | 7 | interfacesProjects("core", "kotlin", "paper", "next") 8 | 9 | fun interfacesProjects(vararg names: String) { 10 | include(*names) 11 | 12 | names.forEach { 13 | project(":$it").name = "interfaces-$it" 14 | } 15 | } 16 | 17 | // Add the example modules. 18 | //include("examples/example-java") 19 | //project(":examples/example-java").name = "example-java" 20 | //include("examples/example-kotlin") 21 | //project(":examples/example-kotlin").name = "example-kotlin" 22 | include("examples/example-next") 23 | project(":examples/example-next").name = "example-next" 24 | 25 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 26 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/view/BukkitNestedRunnable.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.view; 2 | 3 | import org.bukkit.scheduler.BukkitRunnable; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | 6 | import java.util.Set; 7 | 8 | final class BukkitNestedRunnable extends BukkitRunnable { 9 | 10 | private final Set tasks; 11 | private final Runnable runnable; 12 | 13 | BukkitNestedRunnable( 14 | final @NonNull Set tasks, 15 | final @NonNull Runnable runnable 16 | ) { 17 | this.tasks = tasks; 18 | this.runnable = runnable; 19 | } 20 | 21 | @Override 22 | public void run() { 23 | this.runnable.run(); 24 | this.tasks.remove(this.getTaskId()); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/grid/GridPoint.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.grid 2 | 3 | /** 4 | * A 2-dimensional vector storing integer components. 5 | */ 6 | public data class GridPoint( 7 | val x: Int, 8 | val y: Int, 9 | ) { 10 | public companion object { 11 | public fun at( 12 | x: Int, 13 | y: Int, 14 | ): GridPoint = GridPoint(x, y) 15 | } 16 | 17 | public operator fun minus(other: GridPoint): GridPoint = GridPoint(x - other.x, y - other.y) 18 | 19 | public operator fun rangeTo(other: GridPoint): Sequence = 20 | sequence { 21 | for (x in x..other.x) { 22 | for (y in y..other.y) { 23 | yield(at(x, y)) 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /kotlin/src/main/kotlin/org/incendo/interfaces/kotlin/ClickHandlerExt.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.kotlin 2 | 3 | import org.incendo.interfaces.core.click.ClickContext 4 | import org.incendo.interfaces.core.click.ClickHandler 5 | import org.incendo.interfaces.core.pane.Pane 6 | import org.incendo.interfaces.core.view.InterfaceViewer 7 | 8 | /** 9 | * Returns a new {@link ClickHandler} that first executes the current handler, and then executes the 10 | * other handler. 11 | * 12 | * @param other the other handler 13 | * @return a combination of this and the given click handler 14 | */ 15 | public operator fun > ClickHandler< 16 | T, 17 | U, 18 | V, 19 | W, 20 | >.plus( 21 | other: ClickHandler, 22 | ): ClickHandler = this.andThen(other) 23 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/transform/ReactiveTransform.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.transform; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.incendo.interfaces.core.pane.Pane; 5 | import org.incendo.interfaces.core.view.InterfaceViewer; 6 | 7 | /** 8 | * A reactive {@link Transform} that gets updated whenever the associated {@link #properties()} is updated. 9 | * 10 | * @param the pane type 11 | * @param the viewer type 12 | * @param the property type 13 | */ 14 | public interface ReactiveTransform extends Transform { 15 | 16 | /** 17 | * Returns the reactive property that this transform depends on. 18 | * 19 | * @return the property 20 | */ 21 | @NonNull InterfaceProperty[] properties(); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/transform/InterruptUpdateException.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.transform; 2 | 3 | /** 4 | * Exception that can be thrown if a {@link Transform} should cancel the updating of 5 | * an interface. 6 | */ 7 | public class InterruptUpdateException extends RuntimeException { 8 | 9 | /** 10 | * Constructs a new exception instance. 11 | * 12 | * @param reason the reason for interrupting the update 13 | */ 14 | public InterruptUpdateException( 15 | final String reason 16 | ) { 17 | super(reason); 18 | } 19 | 20 | @Override 21 | public final synchronized Throwable fillInStackTrace() { 22 | return this; 23 | } 24 | 25 | @Override 26 | public final synchronized Throwable initCause(final Throwable cause) { 27 | return this; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/transform/DummyInterfaceProperty.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.transform; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | 5 | public final class DummyInterfaceProperty implements InterfaceProperty { 6 | 7 | static final InterfaceProperty INSTANCE = new DummyInterfaceProperty(); 8 | 9 | private final Object object = new Object(); 10 | 11 | private DummyInterfaceProperty() { 12 | } 13 | 14 | @Override 15 | public @NonNull Object get() { 16 | return this.object; 17 | } 18 | 19 | @Override 20 | public void set(final Object value) { 21 | throw new UnsupportedOperationException("Cannot update a dummy interface property"); 22 | } 23 | 24 | @Override 25 | public void addListener(final O reference, @NonNull final TriConsumer consumer) { 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/arguments/MutableInterfaceArguments.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.arguments; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | 5 | /** 6 | * A mutable variant of {@link InterfaceArguments}. 7 | */ 8 | @SuppressWarnings("unused") 9 | public interface MutableInterfaceArguments extends InterfaceArguments { 10 | 11 | /** 12 | * Sets a value of the argument. 13 | * 14 | * @param key the key 15 | * @param value the value 16 | * @param the type 17 | */ 18 | void set( 19 | @NonNull ArgumentKey key, 20 | T value 21 | ); 22 | 23 | /** 24 | * Returns an immutable opy of this interface argument. 25 | * 26 | * @return immutable copy 27 | */ 28 | default @NonNull InterfaceArguments asImmutable() { 29 | return InterfaceArguments.immutable(this); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/transform/Pair.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.transform; 2 | 3 | public class Pair { 4 | 5 | private final U first; 6 | private final V second; 7 | 8 | /** 9 | * Creates a new pair consisting of two elements. 10 | * 11 | * @param first the first element 12 | * @param second the second element 13 | */ 14 | public Pair(final U first, final V second) { 15 | this.first = first; 16 | this.second = second; 17 | } 18 | 19 | /** 20 | * Returns the first element in this pair. 21 | * 22 | * @return the first element 23 | */ 24 | public U getFirst() { 25 | return this.first; 26 | } 27 | 28 | /** 29 | * Returns the second element in this pair. 30 | * 31 | * @return the second element 32 | */ 33 | public V getSecond() { 34 | return this.second; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/view/ContextCompletedPane.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.view; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.incendo.interfaces.core.pane.Pane; 5 | import org.incendo.interfaces.core.transform.TransformContext; 6 | import org.incendo.interfaces.paper.PlayerViewer; 7 | 8 | final class ContextCompletedPane

{ 9 | 10 | private final TransformContext context; 11 | private final P pane; 12 | 13 | ContextCompletedPane( 14 | final @NonNull TransformContext context, 15 | final @NonNull P pane 16 | ) { 17 | this.context = context; 18 | this.pane = pane; 19 | } 20 | 21 | @NonNull TransformContext context() { 22 | return this.context; 23 | } 24 | 25 | @NonNull P pane() { 26 | return this.pane; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/grid/GridMap.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.grid 2 | 3 | public interface GridMap { 4 | public operator fun set( 5 | row: Int, 6 | column: Int, 7 | value: V, 8 | ) 9 | 10 | public operator fun set( 11 | vector: GridPoint, 12 | value: V, 13 | ) { 14 | set(vector.x, vector.y, value) 15 | } 16 | 17 | public operator fun get( 18 | row: Int, 19 | column: Int, 20 | ): V? 21 | 22 | public operator fun get(vector: GridPoint): V? = get(vector.x, vector.y) 23 | 24 | public fun has( 25 | row: Int, 26 | column: Int, 27 | ): Boolean 28 | 29 | public fun has(vector: GridPoint): Boolean = has(vector.x, vector.y) 30 | 31 | public fun forEach(consumer: (row: Int, column: Int, V) -> Unit) 32 | 33 | public suspend fun forEachSuspending(consumer: suspend (row: Int, column: Int, V) -> Unit) 34 | } 35 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/PlayerViewer.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.incendo.interfaces.core.view.InterfaceViewer; 6 | import org.incendo.interfaces.paper.pane.ChestPane; 7 | 8 | /** 9 | * An interface viewer holding a {@link Player}. 10 | *

11 | * Supports {@link ChestPane}. 12 | */ 13 | public interface PlayerViewer extends InterfaceViewer { 14 | 15 | /** 16 | * Returns a new {@code PlayerViewer} containing {@code player}. 17 | * 18 | * @param player the player 19 | * @return the viewer 20 | */ 21 | static @NonNull PlayerViewer of(final @NonNull Player player) { 22 | return new PlayerViewerImpl(player); 23 | } 24 | 25 | /** 26 | * Returns the player. 27 | * 28 | * @return the player 29 | */ 30 | @NonNull Player player(); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/type/Clickable.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.type; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.incendo.interfaces.core.click.ClickContext; 5 | import org.incendo.interfaces.core.click.ClickHandler; 6 | import org.incendo.interfaces.core.pane.Pane; 7 | import org.incendo.interfaces.core.view.InterfaceViewer; 8 | 9 | /** 10 | * Represents a clickable interface. 11 | *

12 | * {@code clickHandler} will be called whenever the viewer clicks on the interface. 13 | * 14 | * @param the pane type 15 | * @param the click cause type 16 | * @param the viewer type 17 | */ 18 | public interface Clickable { 19 | 20 | /** 21 | * Returns the top click handler. 22 | * 23 | * @return the top click handler 24 | */ 25 | @NonNull ClickHandler> clickHandler(); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /kotlin/src/main/kotlin/org/incendo/interfaces/kotlin/MutableInterfaceBuilder.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.kotlin 2 | 3 | import org.incendo.interfaces.core.click.ClickContext 4 | import org.incendo.interfaces.core.click.ClickHandler 5 | import org.incendo.interfaces.core.pane.Pane 6 | import org.incendo.interfaces.core.view.InterfaceViewer 7 | 8 | public interface MutableInterfaceBuilder< 9 | T : Pane, 10 | U, 11 | V : InterfaceViewer, 12 | W : ClickContext, 13 | > { 14 | /** 15 | * Returns a {@code ClickHandler} that cancels the event and then calls the given click handler. 16 | * 17 | * @param clickHandler the handler 18 | * @param pane type 19 | * @param click cause 20 | * @param viewer type 21 | * @param context type 22 | * @return the handler 23 | */ 24 | public fun canceling(clickHandler: ClickHandler = ClickHandler {}): ClickHandler = 25 | ClickHandler.canceling(clickHandler) 26 | } 27 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/utils/SynchronousInterfacesUpdateExecutor.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.utils; 2 | 3 | import java.util.concurrent.ExecutionException; 4 | import java.util.concurrent.Future; 5 | 6 | import org.bukkit.Bukkit; 7 | import org.bukkit.plugin.Plugin; 8 | 9 | public final class SynchronousInterfacesUpdateExecutor implements InterfacesUpdateExecutor { 10 | 11 | @Override 12 | public void execute(final Plugin plugin, final Runnable runnable) { 13 | if (Bukkit.isPrimaryThread()) { 14 | runnable.run(); 15 | return; 16 | } 17 | 18 | Future completionFuture = Bukkit.getScheduler().callSyncMethod(plugin, () -> { 19 | runnable.run(); 20 | return null; 21 | }); 22 | 23 | try { 24 | completionFuture.get(); 25 | } catch (InterruptedException | ExecutionException e) { 26 | throw new RuntimeException(e); 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/view/ChildView.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.view; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.checkerframework.checker.nullness.qual.Nullable; 5 | 6 | /** 7 | * Represents a view which can hold a parent. 8 | */ 9 | @SuppressWarnings("unused") 10 | public interface ChildView { 11 | 12 | /** 13 | * Returns true if this view has a parent, false if not. 14 | * 15 | * @return true if this view has a parent, false if not 16 | */ 17 | boolean hasParent(); 18 | 19 | /** 20 | * Returns the parent view, if any. 21 | * 22 | * @return the parent view, if any 23 | */ 24 | @Nullable PlayerView parent(); 25 | 26 | /** 27 | * Returns to the parent view. 28 | * 29 | * @return the parent view 30 | * @throws NullPointerException if there is no parent view 31 | * @see #hasParent() 32 | * @see #parent() 33 | */ 34 | @NonNull PlayerView back(); 35 | 36 | } 37 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/view/TaskableView.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.view; 2 | 3 | import org.bukkit.plugin.Plugin; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | 6 | import java.util.Collection; 7 | 8 | /** 9 | * Represents an interface view that is able to schedule and execute tasks. 10 | * These tasks are scheduled against itself meaning if the view is closed the task will not be executed. 11 | */ 12 | public interface TaskableView { 13 | 14 | /** 15 | * Add a task to the view 16 | * 17 | * @param plugin the plugin instance to register against 18 | * @param runnable the runnable to execute 19 | * @param delay the ticks to wait before executing 20 | */ 21 | void addTask(@NonNull Plugin plugin, @NonNull Runnable runnable, int delay); 22 | 23 | /** 24 | * Retrieve all scheduled tasks 25 | * 26 | * @return collection of bukkit tasks that have been scheduled 27 | */ 28 | Collection taskIds(); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/inventory/PlayerInterfacesInventory.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.inventory 2 | 3 | import org.bukkit.entity.Player 4 | import org.bukkit.inventory.ItemStack 5 | import org.incendo.interfaces.next.utilities.gridPointToBukkitIndex 6 | 7 | public class PlayerInterfacesInventory( 8 | private val player: Player, 9 | ) : CachedInterfacesInventory() { 10 | private val playerInventory = player.inventory 11 | 12 | override fun get( 13 | row: Int, 14 | column: Int, 15 | ): ItemStack? { 16 | val index = gridPointToBukkitIndex(row, column) 17 | return playerInventory.getItem(index) 18 | } 19 | 20 | override fun setInternal( 21 | row: Int, 22 | column: Int, 23 | item: ItemStack?, 24 | ) { 25 | val index = gridPointToBukkitIndex(row, column) 26 | return playerInventory.setItem(index, item) 27 | } 28 | 29 | override fun isPlayerInventory( 30 | row: Int, 31 | column: Int, 32 | ): Boolean = true 33 | } 34 | -------------------------------------------------------------------------------- /kotlin/src/main/kotlin/org/incendo/interfaces/kotlin/InterfaceViewExt.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.kotlin 2 | 3 | import org.incendo.interfaces.core.Interface 4 | import org.incendo.interfaces.core.arguments.InterfaceArguments 5 | import org.incendo.interfaces.core.pane.Pane 6 | import org.incendo.interfaces.core.view.InterfaceView 7 | import org.incendo.interfaces.core.view.InterfaceViewer 8 | 9 | /** The parent interface. */ 10 | public val InterfaceView.parent: Interface 11 | get() = this.backing() 12 | 13 | /** The viewer of this view. */ 14 | public val InterfaceView<*, *>.viewer: InterfaceViewer 15 | get() = this.viewer() 16 | 17 | /** Whether the viewer is currently viewing this view. */ 18 | public val InterfaceView<*, *>.viewing: Boolean 19 | get() = this.viewing() 20 | 21 | /** The argument provided to this view. */ 22 | public val InterfaceView<*, *>.arguments: InterfaceArguments 23 | get() = this.arguments() 24 | 25 | /** The pane. */ 26 | public val InterfaceView.pane: T 27 | get() = this.pane() 28 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/pane/GridPane.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.pane; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.incendo.interfaces.core.element.Element; 5 | 6 | /** 7 | * A pane with a 2D grid. 8 | * 9 | * @param the pane type 10 | * @param the element type 11 | */ 12 | public interface GridPane extends Pane { 13 | 14 | /** 15 | * Sets an element at the given position. 16 | *

17 | * This method returns an updated instance of this pane with the new element. 18 | * 19 | * @param element the element 20 | * @param x the x coordinate 21 | * @param y the y coordinate 22 | * @return a new pane 23 | */ 24 | @NonNull T element(@NonNull U element, int x, int y); 25 | 26 | /** 27 | * Returns the element at the given position. 28 | * 29 | * @param x the x coordinate 30 | * @param y the y coordinate 31 | * @return the element 32 | */ 33 | @NonNull U element(int x, int y); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/properties/DelegateTrigger.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.properties 2 | 3 | import java.lang.ref.WeakReference 4 | import java.util.concurrent.ConcurrentHashMap 5 | 6 | public open class DelegateTrigger : Trigger { 7 | private val updateListeners = ConcurrentHashMap.newKeySet, Any.() -> Unit>>() 8 | 9 | override fun trigger() { 10 | val iterator = updateListeners.iterator() 11 | while (iterator.hasNext()) { 12 | val (reference, consumer) = iterator.next() 13 | val obj = reference.get() 14 | if (obj == null) { 15 | iterator.remove() 16 | continue 17 | } 18 | obj.apply(consumer) 19 | } 20 | } 21 | 22 | override fun addListener( 23 | reference: T, 24 | listener: T.() -> Unit, 25 | ) { 26 | updateListeners.removeIf { it.first.get() == null } 27 | updateListeners.add(WeakReference(reference) as WeakReference to listener as (Any.() -> Unit)) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/example-next/src/main/kotlin/org/incendo/interfaces/example/next/CatalogueExampleInterface.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.example.next 2 | 3 | import kotlinx.coroutines.runBlocking 4 | import org.bukkit.Material 5 | import org.incendo.interfaces.next.drawable.Drawable 6 | import org.incendo.interfaces.next.element.StaticElement 7 | import org.incendo.interfaces.next.interfaces.Interface 8 | import org.incendo.interfaces.next.interfaces.buildCombinedInterface 9 | 10 | public class CatalogueExampleInterface : RegistrableInterface { 11 | override val subcommand: String = "catalogue" 12 | 13 | override fun create(): Interface<*> = 14 | buildCombinedInterface { 15 | rows = 1 16 | 17 | withTransform { pane, _ -> 18 | pane[3, 3] = 19 | StaticElement( 20 | Drawable.drawable(Material.STICK), 21 | ) { (player) -> 22 | runBlocking { 23 | ChangingTitleExampleInterface().create().open(player) 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/click/clicks/UnknownClick.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.click.clicks; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.incendo.interfaces.core.click.Click; 5 | 6 | /** 7 | * Unknown click type. 8 | * 9 | * @param cause type 10 | */ 11 | public final class UnknownClick implements Click { 12 | 13 | private final T cause; 14 | 15 | UnknownClick( 16 | final @NonNull T cause 17 | ) { 18 | this.cause = cause; 19 | } 20 | 21 | @Override 22 | public @NonNull T cause() { 23 | return this.cause; 24 | } 25 | 26 | @Override 27 | public boolean rightClick() { 28 | return false; 29 | } 30 | 31 | @Override 32 | public boolean leftClick() { 33 | return false; 34 | } 35 | 36 | @Override 37 | public boolean middleClick() { 38 | return false; 39 | } 40 | 41 | @Override 42 | public boolean interact() { 43 | return false; 44 | } 45 | 46 | @Override 47 | public boolean shiftClick() { 48 | return false; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/arguments/ImmutableDelegatingInterfaceArguments.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.arguments; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.checkerframework.checker.nullness.qual.Nullable; 5 | 6 | final class ImmutableDelegatingInterfaceArguments implements InterfaceArguments { 7 | 8 | private final @NonNull InterfaceArguments backingArgument; 9 | 10 | ImmutableDelegatingInterfaceArguments(final @NonNull InterfaceArguments interfaceArguments) { 11 | this.backingArgument = interfaceArguments; 12 | } 13 | 14 | @Override 15 | public @Nullable T get(final @NonNull ArgumentKey key) { 16 | return this.backingArgument.get(key); 17 | } 18 | 19 | @Override 20 | public @NonNull T getOrDefault( 21 | final @NonNull ArgumentKey key, 22 | final @NonNull T def 23 | ) { 24 | return this.backingArgument.getOrDefault(key, def); 25 | } 26 | 27 | @Override 28 | public boolean contains(final @NonNull ArgumentKey key) { 29 | return this.backingArgument.contains(key); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /examples/example-next/src/main/kotlin/org/incendo/interfaces/example/next/MovingExampleInterface.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.example.next 2 | 3 | import org.bukkit.Material 4 | import org.incendo.interfaces.next.drawable.Drawable.Companion.drawable 5 | import org.incendo.interfaces.next.element.StaticElement 6 | import org.incendo.interfaces.next.interfaces.Interface 7 | import org.incendo.interfaces.next.interfaces.buildCombinedInterface 8 | import org.incendo.interfaces.next.utilities.BoundInteger 9 | 10 | public class MovingExampleInterface : RegistrableInterface { 11 | override val subcommand: String = "moving" 12 | 13 | override fun create(): Interface<*> = 14 | buildCombinedInterface { 15 | val countProperty = BoundInteger(4, 1, 7) 16 | var count by countProperty 17 | 18 | rows = 1 19 | 20 | withTransform(countProperty) { pane, _ -> 21 | pane[0, 0] = StaticElement(drawable(Material.RED_CONCRETE)) { count-- } 22 | pane[0, 8] = StaticElement(drawable(Material.GREEN_CONCRETE)) { count++ } 23 | 24 | pane[0, count] = StaticElement(drawable(Material.STICK)) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/example-kotlin/src/main/kotlin/org/incendo/interfaces/example/kotlin/SelectionOptions.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.example.kotlin 2 | 3 | import org.bukkit.Material 4 | 5 | public enum class SelectionOptions( 6 | public val index: Int, 7 | public val material: Material, 8 | public val art: Array> 9 | ) { 10 | ONE( 11 | 1, 12 | Material.IRON_BLOCK, 13 | arrayOf( 14 | 5 to 0, 15 | 6 to 0, 16 | 6 to 1, 17 | 6 to 2, 18 | 6 to 3, 19 | 6 to 4, 20 | ) 21 | ), 22 | TWO( 23 | 2, 24 | Material.GOLD_BLOCK, 25 | arrayOf(5 to 0, 6 to 0, 4 to 1, 7 to 1, 6 to 2, 5 to 3, 4 to 4, 5 to 4, 6 to 4, 7 to 4) 26 | ), 27 | THREE( 28 | 3, 29 | Material.DIAMOND_BLOCK, 30 | arrayOf( 31 | 4 to 0, 32 | 5 to 0, 33 | 6 to 0, 34 | 7 to 0, 35 | 7 to 1, 36 | 4 to 2, 37 | 5 to 2, 38 | 6 to 2, 39 | 7 to 2, 40 | 7 to 3, 41 | 4 to 4, 42 | 5 to 4, 43 | 6 to 4, 44 | 7 to 4 45 | ) 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /gradle/libs.versions.conf: -------------------------------------------------------------------------------- 1 | metadata = { 2 | format = { version = "1.0" } 3 | } 4 | 5 | plugins = { 6 | indra="net.kyori.indra:3.2.0" 7 | indra-publishing="net.kyori.indra.publishing:3.2.0" 8 | indra-publishing-sonatype="net.kyori.indra.publishing.sonatype:3.2.0" 9 | indra-checkstyle="net.kyori.indra.checkstyle:3.2.0" 10 | shadow="com.gradleup.shadow:9.0.2" 11 | dokka="org.jetbrains.dokka:2.0.0" 12 | ktfmt="com.ncorti.ktfmt.gradle:0.6.0" 13 | runPaper="xyz.jpenilla.run-paper:3.0.0-beta.1" 14 | ktlint="org.jlleitschuh.gradle.ktlint:13.1.0" 15 | } 16 | 17 | versions = { 18 | # Tooling 19 | checker-qual = "3.14.0" 20 | kotlin = "2.2.10" 21 | guava = "21.0" 22 | 23 | # Minecraft 24 | adventure-core = "4.24.0" 25 | paper-api = "1.21.4-R0.1-SNAPSHOT" 26 | } 27 | 28 | dependencies = { 29 | # Tooling 30 | checker-qual = { group = "org.checkerframework", name = "checker-qual", version.ref = "checker-qual" } 31 | guava = { group = "com.google.guava", name = "guava", version.ref = "guava" } 32 | 33 | # Misc 34 | adventure-api = { group = "net.kyori", name = "adventure-api", version.ref = "adventure-core" } 35 | paper-api = { group = "io.papermc.paper", name = "paper-api", version.ref = "paper-api" } 36 | } 37 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/pane/Pane.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.pane 2 | 3 | import org.incendo.interfaces.next.element.Element 4 | import org.incendo.interfaces.next.grid.GridMap 5 | import org.incendo.interfaces.next.grid.HashGridMap 6 | 7 | public open class Pane : GridMap { 8 | // This has to be manual redirecting instead of using the by 9 | // syntax as the by syntax breaks the ability to override 10 | // any of the methods! 11 | 12 | private val gridMap = HashGridMap() 13 | 14 | override fun set( 15 | row: Int, 16 | column: Int, 17 | value: Element, 18 | ): Unit = gridMap.set(row, column, value) 19 | 20 | override fun get( 21 | row: Int, 22 | column: Int, 23 | ): Element? = gridMap.get(row, column) 24 | 25 | override fun has( 26 | row: Int, 27 | column: Int, 28 | ): Boolean = gridMap.has(row, column) 29 | 30 | override suspend fun forEachSuspending(consumer: suspend (row: Int, column: Int, Element) -> Unit): Unit = 31 | gridMap.forEachSuspending(consumer) 32 | 33 | override fun forEach(consumer: (row: Int, column: Int, Element) -> Unit): Unit = gridMap.forEach(consumer) 34 | } 35 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/type/ChildTitledInterface.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.type; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.incendo.interfaces.core.arguments.InterfaceArguments; 6 | import org.incendo.interfaces.core.pane.Pane; 7 | import org.incendo.interfaces.core.view.InterfaceView; 8 | import org.incendo.interfaces.core.view.InterfaceViewer; 9 | 10 | /** 11 | * Represents a titled interface. A title should never be null, rather returning an empty component if necessary. 12 | * 13 | * @param the pane type 14 | * @param the viewer type 15 | */ 16 | public interface ChildTitledInterface extends TitledInterface { 17 | 18 | /** 19 | * Opens an interface with a parent view. 20 | * 21 | * @param parent the parent view 22 | * @param arguments the interface's arguments 23 | * @param title the title 24 | * @return the view 25 | */ 26 | @NonNull InterfaceView open( 27 | @NonNull InterfaceView parent, 28 | @NonNull InterfaceArguments arguments, 29 | @NonNull Component title 30 | ); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/click/clicks/MiddleClick.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.click.clicks; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.incendo.interfaces.core.click.Click; 5 | 6 | /** 7 | * A middle-click implementation of {@link Click}. 8 | * 9 | * @param cause type 10 | */ 11 | public final class MiddleClick implements Click { 12 | 13 | private final T cause; 14 | private final boolean shift; 15 | 16 | MiddleClick( 17 | final @NonNull T cause, 18 | final boolean shift 19 | ) { 20 | this.cause = cause; 21 | this.shift = shift; 22 | } 23 | 24 | @Override 25 | public boolean rightClick() { 26 | return false; 27 | } 28 | 29 | @Override 30 | public boolean leftClick() { 31 | return false; 32 | } 33 | 34 | @Override 35 | public boolean middleClick() { 36 | return true; 37 | } 38 | 39 | @Override 40 | public boolean shiftClick() { 41 | return this.shift; 42 | } 43 | 44 | @Override 45 | public boolean interact() { 46 | return false; 47 | } 48 | 49 | @Override 50 | public @NonNull T cause() { 51 | return this.cause; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/inventory/ChestInterfacesInventory.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.inventory 2 | 3 | import net.kyori.adventure.text.Component 4 | import org.bukkit.inventory.Inventory 5 | import org.bukkit.inventory.InventoryHolder 6 | import org.bukkit.inventory.ItemStack 7 | import org.incendo.interfaces.next.utilities.createBukkitInventory 8 | import org.incendo.interfaces.next.utilities.gridPointToBukkitIndex 9 | 10 | public class ChestInterfacesInventory( 11 | holder: InventoryHolder, 12 | title: Component?, 13 | rows: Int, 14 | ) : CachedInterfacesInventory() { 15 | public val chestInventory: Inventory = createBukkitInventory(holder, rows, title) 16 | 17 | override fun get( 18 | row: Int, 19 | column: Int, 20 | ): ItemStack? { 21 | val index = gridPointToBukkitIndex(row, column) 22 | return chestInventory.getItem(index) 23 | } 24 | 25 | override fun setInternal( 26 | row: Int, 27 | column: Int, 28 | item: ItemStack?, 29 | ) { 30 | val index = gridPointToBukkitIndex(row, column) 31 | chestInventory.setItem(index, item) 32 | } 33 | 34 | override fun isPlayerInventory( 35 | row: Int, 36 | column: Int, 37 | ): Boolean = false 38 | } 39 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/arguments/ArgumentKeyImpl.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.arguments; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | 5 | import java.util.Objects; 6 | 7 | class ArgumentKeyImpl implements ArgumentKey { 8 | 9 | private final String key; 10 | private final Class type; 11 | 12 | ArgumentKeyImpl( 13 | final @NonNull String key, 14 | final @NonNull Class type 15 | ) { 16 | this.key = key; 17 | this.type = type; 18 | } 19 | 20 | @Override 21 | public @NonNull String key() { 22 | return this.key; 23 | } 24 | 25 | @Override 26 | public @NonNull Class type() { 27 | return this.type; 28 | } 29 | 30 | @Override 31 | public boolean equals(final Object o) { 32 | if (this == o) { 33 | return true; 34 | } 35 | if (o == null || getClass() != o.getClass()) { 36 | return false; 37 | } 38 | final ArgumentKeyImpl that = (ArgumentKeyImpl) o; 39 | return this.key.equals(that.key) && this.type.equals(that.type); 40 | } 41 | 42 | @Override 43 | public int hashCode() { 44 | return Objects.hash(this.key, this.type); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/utilities/BukkitInventoryUtilities.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.utilities 2 | 3 | import net.kyori.adventure.text.Component 4 | import org.bukkit.Bukkit 5 | import org.bukkit.inventory.Inventory 6 | import org.bukkit.inventory.InventoryHolder 7 | import org.incendo.interfaces.next.grid.GridPoint 8 | import org.incendo.interfaces.next.view.AbstractInterfaceView.Companion.COLUMNS_IN_CHEST 9 | 10 | public fun gridPointToBukkitIndex( 11 | row: Int, 12 | column: Int, 13 | ): Int = row * 9 + column 14 | 15 | public fun gridPointToBukkitIndex(gridPoint: GridPoint): Int = gridPointToBukkitIndex(gridPoint.x, gridPoint.y) 16 | 17 | public fun forEachInGrid( 18 | rows: Int, 19 | columns: Int, 20 | function: (row: Int, column: Int) -> Unit, 21 | ) { 22 | for (row in 0 until rows) { 23 | for (column in 0 until columns) { 24 | function(row, column) 25 | } 26 | } 27 | } 28 | 29 | public fun createBukkitInventory( 30 | holder: InventoryHolder, 31 | rows: Int, 32 | title: Component?, 33 | ): Inventory { 34 | if (title == null) { 35 | return Bukkit.createInventory(holder, rows * COLUMNS_IN_CHEST) 36 | } 37 | 38 | return Bukkit.createInventory(holder, rows * COLUMNS_IN_CHEST, title) 39 | } 40 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/click/clicks/LeftClick.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.click.clicks; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.incendo.interfaces.core.click.Click; 5 | 6 | /** 7 | * A left-click implementation of {@link Click}. 8 | * 9 | * @param cause type 10 | */ 11 | public final class LeftClick implements Click { 12 | 13 | private final T cause; 14 | private final boolean shift; 15 | private final boolean interact; 16 | 17 | LeftClick( 18 | final @NonNull T cause, 19 | final boolean shift, 20 | final boolean interact 21 | ) { 22 | this.cause = cause; 23 | this.shift = shift; 24 | this.interact = interact; 25 | } 26 | 27 | @Override 28 | public boolean rightClick() { 29 | return false; 30 | } 31 | 32 | @Override 33 | public boolean leftClick() { 34 | return true; 35 | } 36 | 37 | @Override 38 | public boolean middleClick() { 39 | return false; 40 | } 41 | 42 | @Override 43 | public boolean shiftClick() { 44 | return this.shift; 45 | } 46 | 47 | @Override 48 | public boolean interact() { 49 | return this.interact; 50 | } 51 | 52 | @Override 53 | public @NonNull T cause() { 54 | return this.cause; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/click/clicks/RightClick.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.click.clicks; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.incendo.interfaces.core.click.Click; 5 | 6 | /** 7 | * A right-click implementation of {@link Click}. 8 | * 9 | * @param cause type 10 | */ 11 | public final class RightClick implements Click { 12 | 13 | private final T cause; 14 | private final boolean shift; 15 | private final boolean interact; 16 | 17 | RightClick( 18 | final @NonNull T cause, 19 | final boolean shift, 20 | final boolean interact 21 | ) { 22 | this.cause = cause; 23 | this.shift = shift; 24 | this.interact = interact; 25 | } 26 | 27 | @Override 28 | public boolean rightClick() { 29 | return true; 30 | } 31 | 32 | @Override 33 | public boolean leftClick() { 34 | return false; 35 | } 36 | 37 | @Override 38 | public boolean middleClick() { 39 | return false; 40 | } 41 | 42 | @Override 43 | public boolean shiftClick() { 44 | return this.shift; 45 | } 46 | 47 | @Override 48 | public boolean interact() { 49 | return this.interact; 50 | } 51 | 52 | @Override 53 | public @NonNull T cause() { 54 | return this.cause; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /examples/example-next/src/main/kotlin/org/incendo/interfaces/example/next/ChangingTitleExampleInterface.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.example.next 2 | 3 | import net.kyori.adventure.text.Component 4 | import org.bukkit.Material 5 | import org.bukkit.inventory.ItemStack 6 | import org.incendo.interfaces.next.drawable.Drawable 7 | import org.incendo.interfaces.next.element.StaticElement 8 | import org.incendo.interfaces.next.interfaces.Interface 9 | import org.incendo.interfaces.next.interfaces.buildCombinedInterface 10 | import org.incendo.interfaces.next.properties.interfaceProperty 11 | 12 | public class ChangingTitleExampleInterface : RegistrableInterface { 13 | override val subcommand: String = "changing-title" 14 | 15 | override fun create(): Interface<*> = 16 | buildCombinedInterface { 17 | rows = 1 18 | 19 | val numberProperty = interfaceProperty(0) 20 | var number by numberProperty 21 | 22 | withTransform(numberProperty) { pane, view -> 23 | view.title(Component.text(number)) 24 | 25 | val item = 26 | ItemStack(Material.STICK) 27 | .name("number -> $number") 28 | 29 | pane[0, 4] = 30 | StaticElement(Drawable.drawable(item)) { 31 | number += 1 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/interfaces/PlayerInterface.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.interfaces 2 | 3 | import org.bukkit.entity.Player 4 | import org.bukkit.event.inventory.InventoryCloseEvent 5 | import org.bukkit.inventory.ItemStack 6 | import org.incendo.interfaces.next.click.ClickHandler 7 | import org.incendo.interfaces.next.pane.PlayerPane 8 | import org.incendo.interfaces.next.transform.AppliedTransform 9 | import org.incendo.interfaces.next.view.InterfaceView 10 | import org.incendo.interfaces.next.view.PlayerInterfaceView 11 | 12 | public class PlayerInterface internal constructor( 13 | override val closeHandlers: MutableMap, 14 | override val transforms: Collection>, 15 | override val clickPreprocessors: Collection, 16 | override val itemPostProcessor: ((ItemStack) -> Unit)?, 17 | ) : Interface { 18 | public companion object { 19 | public const val NUMBER_OF_COLUMNS: Int = 9 20 | } 21 | 22 | override val rows: Int = 4 23 | 24 | override fun createPane(): PlayerPane = PlayerPane() 25 | 26 | override suspend fun open( 27 | player: Player, 28 | parent: InterfaceView?, 29 | ): PlayerInterfaceView { 30 | val view = PlayerInterfaceView(player, this) 31 | view.open() 32 | 33 | return view 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: Build Interfaces 2 | on: 3 | push: 4 | branches: "*" 5 | tags-ignore: [ "*" ] 6 | pull_request: 7 | release: 8 | types: [ released ] 9 | jobs: 10 | build: 11 | # Only run on PRs if the source branch is on someone else's repo 12 | if: ${{ github.event_name != 'pull_request' || github.repository != github.event.pull_request.head.repo.full_name }} 13 | runs-on: "ubuntu-latest" 14 | steps: 15 | - uses: actions/checkout@v5 16 | - name: Set up JDK 17 | uses: actions/setup-java@v4 18 | with: 19 | distribution: 'temurin' 20 | java-version: 21 21 | - uses: gradle/actions/setup-gradle@v4 22 | - name: Build 23 | run: ./gradlew build 24 | - name: Determine status 25 | run: | 26 | if [ "$(./gradlew properties | awk '/^version:/ { print $2; }' | grep '\-SNAPSHOT')" ]; then 27 | echo "STATUS=snapshot" >> $GITHUB_ENV 28 | else 29 | echo "STATUS=release" >> $GITHUB_ENV 30 | fi 31 | - name: "publish" 32 | if: "${{ env.STATUS != 'release' && github.event_name == 'push' && startsWith(github.ref, 'refs/heads/master') }}" 33 | run: "./gradlew publish" 34 | env: 35 | ORG_GRADLE_PROJECT_sonatypeUsername: "${{ secrets.SONATYPE_USERNAME }}" 36 | ORG_GRADLE_PROJECT_sonatypePassword: "${{ secrets.SONATYPE_PASSWORD }}" 37 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/click/Click.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.click; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | 5 | /** 6 | * Represents a class that holds data about a click. 7 | * 8 | * @param the click cause 9 | */ 10 | @SuppressWarnings("unused") 11 | public interface Click { 12 | 13 | /** 14 | * Returns the click cause 15 | * 16 | * @return the click cause 17 | */ 18 | @NonNull T cause(); 19 | 20 | /** 21 | * Returns true if this click was a right click. 22 | * 23 | * @return true if this click was a right click 24 | */ 25 | boolean rightClick(); 26 | 27 | /** 28 | * Returns true if this click was a left click. 29 | * 30 | * @return true if this click was a left click 31 | */ 32 | boolean leftClick(); 33 | 34 | /** 35 | * Returns true if this click was a middle click. 36 | * 37 | * @return true if this click was a middle click 38 | */ 39 | boolean middleClick(); 40 | 41 | /** 42 | * Returns true if this click was a shift click. 43 | * 44 | * @return true if this click was a shift click 45 | */ 46 | boolean shiftClick(); 47 | 48 | /** 49 | * Returns true if this click was triggered by an interact event. 50 | * 51 | * @return true if this click was triggered by an interact event 52 | */ 53 | boolean interact(); 54 | 55 | } 56 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/arguments/ArgumentKey.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.arguments; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | 5 | /** 6 | * A key used as a reference to a value in a {@link InterfaceArguments} instance 7 | * 8 | * @param value type 9 | */ 10 | @SuppressWarnings("unused") 11 | public interface ArgumentKey { 12 | 13 | /** 14 | * Returns a new argument key using the given key and type 15 | * 16 | * @param key the key 17 | * @param type the type 18 | * @param the generic type 19 | * @return the argument key 20 | */ 21 | static @NonNull ArgumentKey of( 22 | final @NonNull String key, 23 | final @NonNull Class type 24 | ) { 25 | return new ArgumentKeyImpl<>(key, type); 26 | } 27 | 28 | /** 29 | * Returns a new object argument key using the given key 30 | * 31 | * @param key the key 32 | * @return the argument key 33 | */ 34 | static @NonNull ArgumentKey of( 35 | final @NonNull String key 36 | ) { 37 | return new ArgumentKeyImpl<>(key, Object.class); 38 | } 39 | 40 | /** 41 | * The name of the key 42 | * 43 | * @return key name 44 | */ 45 | @NonNull String key(); 46 | 47 | /** 48 | * The type represented by the key 49 | * 50 | * @return key type 51 | */ 52 | @NonNull Class type(); 53 | 54 | } 55 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/interfaces/CombinedInterface.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.interfaces 2 | 3 | import net.kyori.adventure.text.Component 4 | import org.bukkit.entity.Player 5 | import org.bukkit.event.inventory.InventoryCloseEvent 6 | import org.bukkit.inventory.ItemStack 7 | import org.incendo.interfaces.next.click.ClickHandler 8 | import org.incendo.interfaces.next.pane.CombinedPane 9 | import org.incendo.interfaces.next.transform.AppliedTransform 10 | import org.incendo.interfaces.next.view.CombinedInterfaceView 11 | import org.incendo.interfaces.next.view.InterfaceView 12 | 13 | public class CombinedInterface internal constructor( 14 | override val rows: Int, 15 | override val initialTitle: Component?, 16 | override val closeHandlers: MutableMap, 17 | override val transforms: Collection>, 18 | override val clickPreprocessors: Collection, 19 | override val itemPostProcessor: ((ItemStack) -> Unit)?, 20 | ) : Interface, 21 | TitledInterface { 22 | override fun totalRows(): Int = rows + 4 23 | 24 | override fun createPane(): CombinedPane = CombinedPane(rows) 25 | 26 | override suspend fun open( 27 | player: Player, 28 | parent: InterfaceView?, 29 | ): CombinedInterfaceView { 30 | val view = CombinedInterfaceView(player, this, parent) 31 | view.open() 32 | 33 | return view 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/arguments/InterfaceArguments.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.arguments; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | 5 | /** 6 | * Holds arguments passed into an interface. 7 | */ 8 | public interface InterfaceArguments { 9 | 10 | /** 11 | * Returns an immutable wrapper of {@link InterfaceArguments}. 12 | * 13 | * @param interfaceArguments the instance to wrap 14 | * @return immutable wrapper 15 | */ 16 | static @NonNull InterfaceArguments immutable(final @NonNull InterfaceArguments interfaceArguments) { 17 | return new ImmutableDelegatingInterfaceArguments(interfaceArguments); 18 | } 19 | 20 | /** 21 | * Returns the value at the given key. 22 | * 23 | * @param key the key 24 | * @param the value's type 25 | * @return the value 26 | */ 27 | T get(@NonNull ArgumentKey key); 28 | 29 | /** 30 | * Returns the value at the given key. 31 | * 32 | * @param key the key 33 | * @param the value's type 34 | * @param def the default object 35 | * @return the value 36 | */ 37 | @NonNull T getOrDefault(@NonNull ArgumentKey key, @NonNull T def); 38 | 39 | /** 40 | * Returns whether the given key is stored in this argument. 41 | * 42 | * @param key the key 43 | * @return whether the key is stored in this argument 44 | */ 45 | boolean contains(@NonNull ArgumentKey key); 46 | 47 | } 48 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/interfaces/ChestInterface.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.interfaces 2 | 3 | import net.kyori.adventure.text.Component 4 | import org.bukkit.entity.Player 5 | import org.bukkit.event.inventory.InventoryCloseEvent 6 | import org.bukkit.inventory.ItemStack 7 | import org.incendo.interfaces.next.click.ClickHandler 8 | import org.incendo.interfaces.next.pane.ChestPane 9 | import org.incendo.interfaces.next.transform.AppliedTransform 10 | import org.incendo.interfaces.next.view.ChestInterfaceView 11 | import org.incendo.interfaces.next.view.InterfaceView 12 | 13 | public class ChestInterface internal constructor( 14 | override val rows: Int, 15 | override val initialTitle: Component?, 16 | override val closeHandlers: MutableMap, 17 | override val transforms: Collection>, 18 | override val clickPreprocessors: Collection, 19 | override val itemPostProcessor: ((ItemStack) -> Unit)?, 20 | ) : Interface, 21 | TitledInterface { 22 | public companion object { 23 | public const val NUMBER_OF_COLUMNS: Int = 9 24 | } 25 | 26 | override fun createPane(): ChestPane = ChestPane() 27 | 28 | override suspend fun open( 29 | player: Player, 30 | parent: InterfaceView?, 31 | ): ChestInterfaceView { 32 | val view = ChestInterfaceView(player, this, parent) 33 | view.open() 34 | 35 | return view 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/grid/HashGridMap.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.grid 2 | 3 | public class HashGridMap : GridMap { 4 | private val backing: MutableMap> = HashMap() 5 | 6 | override fun set( 7 | row: Int, 8 | column: Int, 9 | value: V, 10 | ) { 11 | val rowView = backing.computeIfAbsent(row) { HashMap() } 12 | rowView[column] = value 13 | } 14 | 15 | override fun get( 16 | row: Int, 17 | column: Int, 18 | ): V? { 19 | val rowView = backing[row] ?: return null 20 | return rowView[column] 21 | } 22 | 23 | override fun has( 24 | row: Int, 25 | column: Int, 26 | ): Boolean { 27 | val rowView = backing[row] ?: return false 28 | return rowView.containsKey(column) 29 | } 30 | 31 | override fun forEach(consumer: (Int, Int, V) -> Unit) { 32 | forEachInternal(consumer) 33 | } 34 | 35 | // todo(josh): investigate, workaround for kotlin suspend usage being terrible. 36 | override suspend fun forEachSuspending(consumer: suspend (Int, Int, V) -> Unit) { 37 | forEachInternal { row, column, value -> consumer(row, column, value) } 38 | } 39 | 40 | private inline fun forEachInternal(consumer: (Int, Int, V) -> Unit) { 41 | backing.forEach { (column, rowView) -> 42 | rowView.forEach { (row, value) -> 43 | consumer(column, row, value) 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/pane/PlayerPane.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.pane 2 | 3 | import org.incendo.interfaces.next.element.Element 4 | import org.incendo.interfaces.next.grid.GridPoint 5 | 6 | public class PlayerPane : OrderedPane(PANE_ORDERING) { 7 | internal companion object { 8 | internal val PANE_ORDERING = listOf(1, 2, 3, 0, 4) 9 | 10 | private val OFF_HAND_SLOT = GridPoint.at(4, 4) 11 | } 12 | 13 | public val hotbar: Hotbar = Hotbar() 14 | 15 | public val armor: Armor = Armor() 16 | 17 | public var offHand: Element 18 | get() = get(OFF_HAND_SLOT) ?: Element.EMPTY 19 | set(value) = set(OFF_HAND_SLOT, value) 20 | 21 | // todo(josh): introduce an actual concept of subpanes? 22 | public inner class Hotbar { 23 | public operator fun set( 24 | slot: Int, 25 | value: Element, 26 | ): Unit = set(3, slot, value) 27 | } 28 | 29 | public inner class Armor { 30 | public var helmet: Element 31 | get() = get(4, 3) ?: Element.EMPTY 32 | set(value) = set(4, 3, value) 33 | 34 | public var chest: Element 35 | get() = get(4, 2) ?: Element.EMPTY 36 | set(value) = set(4, 2, value) 37 | 38 | public var leggings: Element 39 | get() = get(4, 1) ?: Element.EMPTY 40 | set(value) = set(4, 1, value) 41 | 42 | public var boots: Element 43 | get() = get(4, 0) ?: Element.EMPTY 44 | set(value) = set(4, 0, value) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/click/ClickHandler.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.click 2 | 3 | import kotlinx.coroutines.CompletableDeferred 4 | import kotlinx.coroutines.CompletionHandler 5 | import kotlinx.coroutines.cancel 6 | 7 | public fun interface ClickHandler { 8 | public companion object { 9 | public val EMPTY: ClickHandler = ClickHandler { } 10 | public val ALLOW: ClickHandler = ClickHandler { cancelled = false } 11 | 12 | public fun process( 13 | clickHandler: ClickHandler, 14 | context: ClickContext, 15 | ): Unit = 16 | with(clickHandler) { 17 | CompletableClickHandler().handle(context) 18 | } 19 | } 20 | 21 | public fun CompletableClickHandler.handle(context: ClickContext) 22 | } 23 | 24 | public class CompletableClickHandler { 25 | private val deferred = CompletableDeferred(null) 26 | 27 | public var cancelled: Boolean = true 28 | public var completingLater: Boolean = false 29 | 30 | public fun complete(): Boolean { 31 | if (deferred.isCancelled || deferred.isCompleted) return false 32 | return deferred.complete(Unit) 33 | } 34 | 35 | public fun cancel() { 36 | if (deferred.isCancelled || deferred.isCompleted) return 37 | deferred.cancel("Cancelled click handler after 6s timeout") 38 | } 39 | 40 | public fun onComplete(handler: CompletionHandler): CompletableClickHandler { 41 | deferred.invokeOnCompletion(handler) 42 | return this 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/transform/InterfaceProperty.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.transform; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | 5 | public interface InterfaceProperty { 6 | 7 | /** 8 | * Returns a new interface property with the given initial value 9 | * 10 | * @param value initial value 11 | * @param type of the value 12 | * @return the property 13 | */ 14 | static @NonNull InterfaceProperty of(final T value) { 15 | return new InterfacePropertyImpl<>(value); 16 | } 17 | 18 | /** 19 | * Returns an interface property that never updates. 20 | * 21 | * @return the property 22 | */ 23 | static @NonNull InterfaceProperty dummy() { 24 | return DummyInterfaceProperty.INSTANCE; 25 | } 26 | 27 | /** 28 | * Returns the current value of the property 29 | * 30 | * @return current value 31 | */ 32 | T get(); 33 | 34 | /** 35 | * Sets the new value of the property 36 | * 37 | * @param value new value 38 | */ 39 | void set(T value); 40 | 41 | /** 42 | * Adds a listener that gets invoked whenever the property is updated 43 | * 44 | * @param the type of the reference 45 | * @param reference an object to reference, if this object is garbage collected 46 | * so is this listener 47 | * @param consumer the consumer 48 | */ 49 | void addListener(O reference, @NonNull TriConsumer consumer); 50 | } 51 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/interfaces/Interface.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.interfaces 2 | 3 | import org.bukkit.entity.Player 4 | import org.bukkit.event.inventory.InventoryCloseEvent 5 | import org.bukkit.inventory.ItemStack 6 | import org.incendo.interfaces.next.InterfacesListeners 7 | import org.incendo.interfaces.next.click.ClickHandler 8 | import org.incendo.interfaces.next.pane.Pane 9 | import org.incendo.interfaces.next.transform.AppliedTransform 10 | import org.incendo.interfaces.next.view.InterfaceView 11 | 12 | public interface Interface

{ 13 | public val rows: Int 14 | 15 | public val closeHandlers: MutableMap 16 | 17 | public val transforms: Collection> 18 | 19 | public val clickPreprocessors: Collection 20 | 21 | public val itemPostProcessor: ((ItemStack) -> Unit)? 22 | 23 | public fun totalRows(): Int = rows 24 | 25 | public fun createPane(): P 26 | 27 | /** 28 | * Opens an [InterfaceView] from this [Interface]. The parent defaults to whatever menu the player 29 | * is currently viewing. 30 | * 31 | * @param player the player to show the view 32 | * @param parent the parent view that is opening the interface 33 | * @return the view 34 | */ 35 | public suspend fun open( 36 | player: Player, 37 | parent: InterfaceView? = 38 | InterfacesListeners.instance.convertHolderToInterfaceView(player.openInventory.topInventory.holder), 39 | ): InterfaceView 40 | } 41 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/element/TextElement.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.element; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.incendo.interfaces.core.element.Element; 6 | 7 | import java.util.Objects; 8 | 9 | /** 10 | * An element containing a piece of text. 11 | */ 12 | public class TextElement implements Element { 13 | 14 | private final @NonNull Component text; 15 | 16 | /** 17 | * Constructs {@code TextElement}. 18 | * 19 | * @param text the text 20 | */ 21 | public TextElement(final @NonNull Component text) { 22 | this.text = text; 23 | } 24 | 25 | /** 26 | * Creates a new TextElement. 27 | * 28 | * @param text the text 29 | * @return the element 30 | */ 31 | public static @NonNull TextElement of(final @NonNull Component text) { 32 | return new TextElement(text); 33 | } 34 | 35 | /** 36 | * Returns the element's text. 37 | * 38 | * @return the element's text 39 | */ 40 | public @NonNull Component text() { 41 | return this.text; 42 | } 43 | 44 | @Override 45 | public final boolean equals(final Object o) { 46 | if (this == o) { 47 | return true; 48 | } 49 | if (o == null || getClass() != o.getClass()) { 50 | return false; 51 | } 52 | TextElement that = (TextElement) o; 53 | return this.text.equals(that.text); 54 | } 55 | 56 | @Override 57 | public final int hashCode() { 58 | return Objects.hash(this.text); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/Constants.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next 2 | 3 | import kotlinx.coroutines.CoroutineName 4 | import kotlinx.coroutines.CoroutineScope 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.SupervisorJob 7 | import kotlinx.coroutines.asCoroutineDispatcher 8 | import java.util.concurrent.Executors 9 | import java.util.concurrent.atomic.AtomicInteger 10 | 11 | internal object Constants { 12 | internal val SCOPE = 13 | CoroutineScope( 14 | CoroutineName("interfaces") + 15 | SupervisorJob() + 16 | run { 17 | val threadNumber = AtomicInteger() 18 | val factory = { runnable: Runnable -> 19 | Thread("interfaces-${threadNumber.getAndIncrement()}").apply { 20 | isDaemon = true 21 | } 22 | } 23 | 24 | System 25 | .getProperty("org.incendo.interfaces.next.fixedPoolSize") 26 | ?.toIntOrNull() 27 | ?.coerceAtLeast(2) 28 | ?.let { size -> 29 | if (System.getProperty("org.incendo.interfaces.next.useScheduledPool").toBoolean()) { 30 | Executors.newScheduledThreadPool(size, factory) 31 | } else { 32 | Executors.newFixedThreadPool(size, factory) 33 | } 34 | }?.asCoroutineDispatcher() 35 | ?: Dispatchers.Default 36 | }, 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/view/ChestInterfaceView.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.view 2 | 3 | import net.kyori.adventure.text.Component 4 | import org.bukkit.entity.Player 5 | import org.bukkit.inventory.Inventory 6 | import org.bukkit.inventory.InventoryHolder 7 | import org.incendo.interfaces.next.interfaces.ChestInterface 8 | import org.incendo.interfaces.next.inventory.ChestInterfacesInventory 9 | import org.incendo.interfaces.next.pane.ChestPane 10 | import org.incendo.interfaces.next.utilities.TitleState 11 | 12 | public class ChestInterfaceView internal constructor( 13 | player: Player, 14 | backing: ChestInterface, 15 | parent: InterfaceView?, 16 | ) : AbstractInterfaceView( 17 | player, 18 | backing, 19 | parent, 20 | ), 21 | InventoryHolder { 22 | private val titleState = TitleState(backing.initialTitle) 23 | 24 | override fun title(value: Component) { 25 | titleState.current = value 26 | } 27 | 28 | override fun createInventory(): ChestInterfacesInventory = 29 | ChestInterfacesInventory( 30 | this, 31 | titleState.current, 32 | backing.rows, 33 | ) 34 | 35 | override fun openInventory() { 36 | player.openInventory(this.inventory) 37 | } 38 | 39 | override fun requiresPlayerUpdate(): Boolean = false 40 | 41 | override fun requiresNewInventory(): Boolean = super.requiresNewInventory() || titleState.hasChanged 42 | 43 | override fun getInventory(): Inventory = currentInventory.chestInventory 44 | 45 | override fun isOpen(player: Player): Boolean = player.openInventory.topInventory.holder == this 46 | } 47 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/view/ViewOpenEvent.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.view; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.event.HandlerList; 5 | import org.bukkit.event.player.PlayerEvent; 6 | import org.checkerframework.checker.nullness.qual.NonNull; 7 | import org.incendo.interfaces.core.view.InterfaceView; 8 | import org.incendo.interfaces.paper.PlayerViewer; 9 | import org.incendo.interfaces.paper.utils.EventUtil; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | /** 13 | * Event emitted when a {@link InterfaceView} is opened. 14 | */ 15 | public final class ViewOpenEvent extends PlayerEvent { 16 | 17 | private static final HandlerList handlers = new HandlerList(); 18 | 19 | private final @NotNull InterfaceView view; 20 | 21 | /** 22 | * Construct a new {@code ViewOpenEvent} 23 | * 24 | * @param view the view 25 | */ 26 | public ViewOpenEvent(final @NotNull InterfaceView view) { 27 | super(view.viewer().player()); 28 | EventUtil.setAsync(this, !Bukkit.isPrimaryThread()); 29 | this.view = view; 30 | } 31 | 32 | /** 33 | * Returns the view 34 | * 35 | * @return the view 36 | */ 37 | public @NonNull InterfaceView view() { 38 | return this.view; 39 | } 40 | 41 | /** 42 | * Returns the handler list 43 | * 44 | * @return the handler list 45 | */ 46 | @NotNull 47 | public static @NonNull HandlerList getHandlerList() { 48 | return handlers; 49 | } 50 | 51 | @Override 52 | public @NotNull HandlerList getHandlers() { 53 | return handlers; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/view/ViewCloseEvent.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.view; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.event.HandlerList; 5 | import org.bukkit.event.player.PlayerEvent; 6 | import org.checkerframework.checker.nullness.qual.NonNull; 7 | import org.incendo.interfaces.core.view.InterfaceView; 8 | import org.incendo.interfaces.paper.PlayerViewer; 9 | import org.incendo.interfaces.paper.utils.EventUtil; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | /** 13 | * Event emitted when a {@link InterfaceView} is closed. 14 | */ 15 | public final class ViewCloseEvent extends PlayerEvent { 16 | 17 | private static final HandlerList handlers = new HandlerList(); 18 | 19 | private final @NotNull InterfaceView view; 20 | 21 | /** 22 | * Construct a new {@code ViewCloseEvent} 23 | * 24 | * @param view the view 25 | */ 26 | public ViewCloseEvent(final @NotNull InterfaceView view) { 27 | super(view.viewer().player()); 28 | EventUtil.setAsync(this, !Bukkit.isPrimaryThread()); 29 | this.view = view; 30 | } 31 | 32 | /** 33 | * Returns the view 34 | * 35 | * @return the view 36 | */ 37 | public @NonNull InterfaceView view() { 38 | return this.view; 39 | } 40 | 41 | /** 42 | * Returns the handler list 43 | * 44 | * @return the handler list 45 | */ 46 | @NotNull 47 | public static @NonNull HandlerList getHandlerList() { 48 | return handlers; 49 | } 50 | 51 | @Override 52 | public @NotNull HandlerList getHandlers() { 53 | return handlers; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/type/TitledInterface.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.type; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.incendo.interfaces.core.Interface; 6 | import org.incendo.interfaces.core.arguments.InterfaceArguments; 7 | import org.incendo.interfaces.core.pane.Pane; 8 | import org.incendo.interfaces.core.view.InterfaceView; 9 | import org.incendo.interfaces.core.view.InterfaceViewer; 10 | 11 | /** 12 | * Represents a titled interface. A title should never be null, rather returning an empty component if necessary. 13 | * 14 | * @param the pane type 15 | * @param the viewer type 16 | */ 17 | public interface TitledInterface extends Interface { 18 | 19 | /** 20 | * Returns the title of this interface. 21 | * 22 | * @return the title 23 | */ 24 | @NonNull Component title(); 25 | 26 | /** 27 | * Opens this interface to the viewer. 28 | * 29 | * @param viewer the viewer 30 | * @param title the title 31 | * @return the view 32 | */ 33 | @NonNull InterfaceView open( 34 | @NonNull U viewer, 35 | @NonNull Component title 36 | ); 37 | 38 | /** 39 | * Opens this interface to the viewer. 40 | * 41 | * @param viewer the viewer 42 | * @param arguments the interface's arguments 43 | * @param title the title 44 | * @return the view 45 | */ 46 | @NonNull InterfaceView open( 47 | @NonNull U viewer, 48 | @NonNull InterfaceArguments arguments, 49 | @NonNull Component title 50 | ); 51 | 52 | } 53 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/utils/EventUtil.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.utils; 2 | 3 | import org.bukkit.event.Event; 4 | import org.checkerframework.checker.nullness.qual.NonNull; 5 | import org.checkerframework.framework.qual.DefaultQualifier; 6 | 7 | import java.lang.reflect.Field; 8 | 9 | @DefaultQualifier(NonNull.class) 10 | public final class EventUtil { 11 | 12 | private static final Field EVENT_IS_ASYNC_FIELD; 13 | 14 | static { 15 | Field field; 16 | try { 17 | try { 18 | field = Event.class.getDeclaredField("async"); 19 | } catch (final NoSuchFieldException e0) { 20 | try { 21 | field = Event.class.getDeclaredField("isAsync"); 22 | } catch (final NoSuchFieldException e1) { 23 | e1.addSuppressed(e0); 24 | throw e1; 25 | } 26 | } 27 | field.setAccessible(true); 28 | } catch (final Throwable thr) { 29 | throw new RuntimeException("Failed to find async/isAsync field in Event class", thr); 30 | } 31 | EVENT_IS_ASYNC_FIELD = field; 32 | } 33 | 34 | private EventUtil() { 35 | } 36 | 37 | /** 38 | * Set the private final async field for when an appropriate constructor isn't available. 39 | * 40 | * @param event event 41 | * @param async value 42 | */ 43 | public static void setAsync(final Event event, final boolean async) { 44 | try { 45 | EVENT_IS_ASYNC_FIELD.set(event, async); 46 | } catch (final ReflectiveOperationException ex) { 47 | throw new RuntimeException(ex); 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/view/CombinedInterfaceView.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.view 2 | 3 | import net.kyori.adventure.text.Component 4 | import org.bukkit.entity.Player 5 | import org.bukkit.inventory.Inventory 6 | import org.bukkit.inventory.InventoryHolder 7 | import org.incendo.interfaces.next.interfaces.CombinedInterface 8 | import org.incendo.interfaces.next.inventory.CombinedInterfacesInventory 9 | import org.incendo.interfaces.next.pane.CombinedPane 10 | import org.incendo.interfaces.next.utilities.TitleState 11 | 12 | public class CombinedInterfaceView internal constructor( 13 | player: Player, 14 | backing: CombinedInterface, 15 | parent: InterfaceView?, 16 | ) : AbstractInterfaceView( 17 | player, 18 | backing, 19 | parent, 20 | ), 21 | InventoryHolder { 22 | private val titleState = TitleState(backing.initialTitle) 23 | 24 | override fun title(value: Component) { 25 | titleState.current = value 26 | } 27 | 28 | override fun createInventory(): CombinedInterfacesInventory = 29 | CombinedInterfacesInventory( 30 | this, 31 | player, 32 | titleState.current, 33 | backing.rows, 34 | ) 35 | 36 | override fun openInventory() { 37 | player.openInventory(this.inventory) 38 | } 39 | 40 | override fun requiresPlayerUpdate(): Boolean = false 41 | 42 | override fun requiresNewInventory(): Boolean = super.requiresNewInventory() || titleState.hasChanged 43 | 44 | override fun getInventory(): Inventory = currentInventory.chestInventory 45 | 46 | override fun isOpen(player: Player): Boolean = player.openInventory.topInventory.holder == this 47 | } 48 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/utils/PaperUtils.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.utils; 2 | 3 | import com.google.common.base.Suppliers; 4 | 5 | import java.util.function.Supplier; 6 | 7 | import org.incendo.interfaces.core.util.Vector2; 8 | 9 | /** 10 | * A collection of utility methods. 11 | */ 12 | @SuppressWarnings("unused") 13 | public final class PaperUtils { 14 | 15 | private static final Supplier PAPER = Suppliers.memoize(() -> { 16 | try { 17 | Class.forName("com.destroystokyo.paper.PaperConfig"); 18 | return true; 19 | } catch (final ClassNotFoundException ignore) { 20 | } 21 | try { 22 | Class.forName("io.papermc.paper.configuration.PaperConfigurations"); 23 | return true; 24 | } catch (final ClassNotFoundException ignore) { 25 | } 26 | return false; 27 | }); 28 | 29 | /** 30 | * Converts a Bukkit slot index to an x/y position. 31 | * 32 | * @param slot the slot 33 | * @return the x/y position 34 | */ 35 | public static Vector2 slotToGrid(final int slot) { 36 | return Vector2.at(slot % 9, slot / 9); 37 | } 38 | 39 | /** 40 | * Converts the x/y position to a Bukkit slot index. 41 | * 42 | * @param vector the vector 43 | * @return the slot 44 | */ 45 | public static int gridToSlot(final Vector2 vector) { 46 | return vector.y() * 9 + vector.x(); 47 | } 48 | 49 | /** 50 | * Check if the currently running server is Paper-based. 51 | * 52 | * @return if the current server is Paper-based 53 | */ 54 | public static boolean isPaper() { 55 | return PAPER.get(); 56 | } 57 | 58 | private PaperUtils() {} 59 | } 60 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/utilities/CollapsablePaneMap.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.utilities 2 | 3 | import org.incendo.interfaces.next.pane.CompletedPane 4 | import org.incendo.interfaces.next.pane.Pane 5 | import org.incendo.interfaces.next.pane.convertToEmptyCompletedPaneAndFill 6 | 7 | internal class CollapsablePaneMap private constructor( 8 | private val rows: Int, 9 | // pass in a pane from the view so that we can persist 10 | // ordering, used in the listeners. 11 | private val basePane: Pane, 12 | // privately pass in a map here so that we can use 13 | // super methods when overriding methods in the delegate. 14 | private val internal: MutableMap, 15 | ) : MutableMap by internal { 16 | internal companion object { 17 | internal fun create( 18 | rows: Int, 19 | basePane: Pane, 20 | ) = CollapsablePaneMap( 21 | rows, 22 | basePane, 23 | sortedMapOf(Comparator.reverseOrder()), 24 | ) 25 | } 26 | 27 | private var cachedPane: CompletedPane? = null 28 | 29 | override fun put( 30 | key: Int, 31 | value: CompletedPane, 32 | ): CompletedPane? { 33 | cachedPane = null 34 | return internal.put(key, value) 35 | } 36 | 37 | internal fun collapse(): CompletedPane { 38 | cachedPane?.let { pane -> 39 | return pane 40 | } 41 | 42 | val pane = basePane.convertToEmptyCompletedPaneAndFill(rows) 43 | 44 | val current = internal.toMap().values 45 | 46 | current.forEach { layer -> 47 | layer.forEach { row, column, value -> 48 | pane[row, column] = value 49 | } 50 | } 51 | 52 | return pane 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/utilities/BoundInteger.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.utilities 2 | 3 | import org.incendo.interfaces.next.properties.DelegateTrigger 4 | import org.incendo.interfaces.next.properties.Trigger 5 | import kotlin.properties.ObservableProperty 6 | import kotlin.reflect.KProperty 7 | 8 | // todo(josh): recalculate value when max/min changed? 9 | public class BoundInteger( 10 | initial: Int, 11 | public var min: Int, 12 | public var max: Int, 13 | ) : ObservableProperty(initial), 14 | Trigger { 15 | private val delegateTrigger = DelegateTrigger() 16 | 17 | override fun beforeChange( 18 | property: KProperty<*>, 19 | oldValue: Int, 20 | newValue: Int, 21 | ): Boolean { 22 | val acceptableRange = min..max 23 | 24 | if (newValue in acceptableRange) { 25 | return true 26 | } 27 | 28 | val coercedValue = newValue.coerceIn(acceptableRange) 29 | var value by this 30 | 31 | value = coercedValue 32 | 33 | return false 34 | } 35 | 36 | override fun afterChange( 37 | property: KProperty<*>, 38 | oldValue: Int, 39 | newValue: Int, 40 | ) { 41 | if (oldValue != newValue) { 42 | trigger() 43 | } 44 | } 45 | 46 | override fun trigger() { 47 | delegateTrigger.trigger() 48 | } 49 | 50 | override fun addListener( 51 | reference: T, 52 | listener: T.() -> Unit, 53 | ) { 54 | delegateTrigger.addListener(reference, listener) 55 | } 56 | 57 | public fun hasSucceeding(): Boolean { 58 | val value by this 59 | return value < max 60 | } 61 | 62 | public fun hasPreceeding(): Boolean { 63 | val value by this 64 | return value > min 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/pane/CompletedPane.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.pane 2 | 3 | import org.bukkit.entity.Player 4 | import org.incendo.interfaces.next.click.ClickHandler 5 | import org.incendo.interfaces.next.element.CompletedElement 6 | import org.incendo.interfaces.next.element.complete 7 | import org.incendo.interfaces.next.grid.GridMap 8 | import org.incendo.interfaces.next.grid.GridPoint 9 | import org.incendo.interfaces.next.grid.HashGridMap 10 | import org.incendo.interfaces.next.utilities.forEachInGrid 11 | import org.incendo.interfaces.next.view.AbstractInterfaceView.Companion.COLUMNS_IN_CHEST 12 | 13 | internal open class CompletedPane : GridMap by HashGridMap() { 14 | internal open fun getRaw(vector: GridPoint): CompletedElement? = get(vector) 15 | } 16 | 17 | internal class CompletedOrderedPane( 18 | private val ordering: List, 19 | ) : CompletedPane() { 20 | override fun getRaw(vector: GridPoint): CompletedElement? = get(ordering[vector.x], vector.y) 21 | } 22 | 23 | internal suspend fun Pane.complete(player: Player): CompletedPane { 24 | val pane = convertToEmptyCompletedPane() 25 | 26 | forEachSuspending { row, column, element -> 27 | pane[row, column] = element.complete(player) 28 | } 29 | 30 | return pane 31 | } 32 | 33 | internal fun Pane.convertToEmptyCompletedPaneAndFill(rows: Int): CompletedPane { 34 | val pane = convertToEmptyCompletedPane() 35 | val airElement = CompletedElement(null, ClickHandler.EMPTY) 36 | 37 | forEachInGrid(rows, COLUMNS_IN_CHEST) { row, column -> 38 | pane[row, column] = airElement 39 | } 40 | 41 | return pane 42 | } 43 | 44 | internal fun Pane.convertToEmptyCompletedPane(): CompletedPane { 45 | if (this is OrderedPane) { 46 | return CompletedOrderedPane(ordering) 47 | } 48 | 49 | return CompletedPane() 50 | } 51 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/inventory/CombinedInterfacesInventory.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.inventory 2 | 3 | import net.kyori.adventure.text.Component 4 | import org.bukkit.entity.Player 5 | import org.bukkit.inventory.Inventory 6 | import org.bukkit.inventory.InventoryHolder 7 | import org.bukkit.inventory.ItemStack 8 | import org.incendo.interfaces.next.utilities.createBukkitInventory 9 | import org.incendo.interfaces.next.utilities.gridPointToBukkitIndex 10 | import org.incendo.interfaces.next.view.AbstractInterfaceView.Companion.COLUMNS_IN_CHEST 11 | 12 | public class CombinedInterfacesInventory( 13 | holder: InventoryHolder, 14 | player: Player, 15 | title: Component?, 16 | private val rows: Int, 17 | ) : CachedInterfacesInventory() { 18 | private val chestSlots = rows * COLUMNS_IN_CHEST 19 | 20 | private val playerInventory = player.inventory 21 | public val chestInventory: Inventory = createBukkitInventory(holder, rows, title) 22 | 23 | override fun get( 24 | row: Int, 25 | column: Int, 26 | ): ItemStack? { 27 | val bukkitIndex = gridPointToBukkitIndex(row, column) 28 | 29 | if (row >= rows) { 30 | val adjustedIndex = bukkitIndex - chestSlots 31 | return playerInventory.getItem(adjustedIndex) 32 | } 33 | 34 | return chestInventory.getItem(bukkitIndex) 35 | } 36 | 37 | override fun setInternal( 38 | row: Int, 39 | column: Int, 40 | item: ItemStack?, 41 | ) { 42 | val bukkitIndex = gridPointToBukkitIndex(row, column) 43 | 44 | if (row >= rows) { 45 | val adjustedIndex = bukkitIndex - chestSlots 46 | playerInventory.setItem(adjustedIndex, item) 47 | return 48 | } 49 | 50 | chestInventory.setItem(bukkitIndex, item) 51 | } 52 | 53 | override fun isPlayerInventory( 54 | row: Int, 55 | column: Int, 56 | ): Boolean = row >= rows 57 | } 58 | -------------------------------------------------------------------------------- /kotlin/src/main/kotlin/org/incendo/interfaces/kotlin/Arguments.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.kotlin 2 | 3 | import org.incendo.interfaces.core.arguments.ArgumentKey 4 | import org.incendo.interfaces.core.arguments.HashMapInterfaceArguments 5 | import org.incendo.interfaces.core.arguments.InterfaceArguments 6 | import org.incendo.interfaces.core.arguments.MutableInterfaceArguments 7 | 8 | /** 9 | * Returns a new [MutableInterfaceArguments] with the given [entries]. 10 | * 11 | * @param entries the entries 12 | * @return the created argument 13 | */ 14 | public fun mutableInterfaceArgumentOf(vararg entries: ArgumentPair<*>): MutableInterfaceArguments { 15 | val builder = HashMapInterfaceArguments.Builder() 16 | 17 | entries.forEach { (key, value) -> 18 | // erase the type 19 | builder.with(ArgumentKey.of(key.key()), value) 20 | } 21 | 22 | return builder.build() 23 | } 24 | 25 | /** 26 | * Returns a new [InterfaceArguments] with the given [entries]. 27 | * 28 | * @param entries the entries 29 | * @return the created argument 30 | */ 31 | public fun interfaceArgumentOf(vararg entries: ArgumentPair<*>): InterfaceArguments = 32 | mutableInterfaceArgumentOf(*entries).asImmutable() 33 | 34 | /** 35 | * Creates a new [ArgumentKey] using the given type [T] 36 | * 37 | * @param key the key 38 | * @param T the type 39 | * @return the argument key 40 | */ 41 | public inline fun argumentKeyOf(key: String): ArgumentKey = ArgumentKey.of(key, T::class.java) 42 | 43 | /** 44 | * Creates a new [ArgumentPair] using the given type T 45 | * 46 | * @param key the key 47 | * @param value the value 48 | * @param T the type 49 | * @return the argument pair 50 | */ 51 | public inline fun argumentPairOf( 52 | key: String, 53 | value: T, 54 | ): ArgumentPair = argumentKeyOf(key) to value 55 | 56 | public data class ArgumentPair( 57 | public val key: ArgumentKey, 58 | public val value: T, 59 | ) 60 | 61 | public infix fun ArgumentKey.to(value: T): ArgumentPair = ArgumentPair(this, value) 62 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/transform/builtin/PagedTransformation.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.transform.builtin 2 | 3 | import org.bukkit.entity.Player 4 | import org.bukkit.event.inventory.ClickType 5 | import org.incendo.interfaces.next.drawable.Drawable 6 | import org.incendo.interfaces.next.element.StaticElement 7 | import org.incendo.interfaces.next.grid.GridPoint 8 | import org.incendo.interfaces.next.pane.Pane 9 | import org.incendo.interfaces.next.properties.Trigger 10 | import org.incendo.interfaces.next.transform.ReactiveTransform 11 | import org.incendo.interfaces.next.utilities.BoundInteger 12 | import org.incendo.interfaces.next.view.InterfaceView 13 | 14 | public abstract class PagedTransformation

( 15 | private val back: PaginationButton, 16 | private val forward: PaginationButton, 17 | extraTriggers: Array = emptyArray(), 18 | ) : ReactiveTransform

{ 19 | protected val boundPage: BoundInteger = BoundInteger(0, 0, Integer.MAX_VALUE) 20 | protected var page: Int by boundPage 21 | 22 | override suspend fun invoke( 23 | pane: P, 24 | view: InterfaceView, 25 | ) { 26 | if (boundPage.hasPreceeding()) { 27 | applyButton(pane, back) 28 | } 29 | 30 | if (boundPage.hasSucceeding()) { 31 | applyButton(pane, forward) 32 | } 33 | } 34 | 35 | protected open fun applyButton( 36 | pane: Pane, 37 | button: PaginationButton, 38 | ) { 39 | val (point, drawable, increments) = button 40 | 41 | pane[point] = 42 | StaticElement(drawable) { (player, _, click) -> 43 | increments[click]?.let { increment -> page += increment } 44 | button.clickHandler(player) 45 | } 46 | } 47 | 48 | override val triggers: Array = arrayOf(boundPage).plus(extraTriggers) 49 | } 50 | 51 | public data class PaginationButton( 52 | public val position: GridPoint, 53 | public val drawable: Drawable, 54 | public val increments: Map, 55 | public val clickHandler: (Player) -> Unit = {}, 56 | ) 57 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/transform/builtin/PaginationTransformation.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.transform.builtin 2 | 3 | import org.incendo.interfaces.next.element.Element 4 | import org.incendo.interfaces.next.grid.GridPositionGenerator 5 | import org.incendo.interfaces.next.pane.Pane 6 | import org.incendo.interfaces.next.properties.Trigger 7 | import org.incendo.interfaces.next.view.InterfaceView 8 | import kotlin.math.ceil 9 | import kotlin.properties.Delegates 10 | 11 | public open class PaginationTransformation

( 12 | private val positionGenerator: GridPositionGenerator, 13 | default: Collection, 14 | back: PaginationButton, 15 | forward: PaginationButton, 16 | extraTriggers: Array = emptyArray(), 17 | ) : PagedTransformation

(back, forward, extraTriggers) { 18 | private val values by Delegates.observable(default.toList()) { _, _, _ -> 19 | boundPage.max = maxPages() 20 | } 21 | 22 | init { 23 | boundPage.max = maxPages() 24 | } 25 | 26 | override suspend fun invoke( 27 | pane: P, 28 | view: InterfaceView, 29 | ) { 30 | val positions = positionGenerator.generate() 31 | val slots = positions.size 32 | 33 | val offset = page * slots 34 | 35 | positions.forEachIndexed { index, point -> 36 | val currentIndex = index + offset 37 | 38 | if (currentIndex >= values.size) { 39 | return@forEachIndexed 40 | } 41 | 42 | pane[point] = values[currentIndex] 43 | } 44 | 45 | super.invoke(pane, view) 46 | } 47 | 48 | private fun maxPages(): Int { 49 | val amount = values.size 50 | val slotsPerPage = positionGenerator.generate().size 51 | val rawPages = (amount.toDouble() / slotsPerPage.toDouble()) 52 | 53 | // Round up the amount so we find the amount of pages needed 54 | // then we do - 1 because we zero-index the pages. Ensure the 55 | // value is at least 0 in case rawPages is 0.0. 56 | return (ceil(rawPages).toInt() - 1).coerceAtLeast(0) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/view/InterfaceView.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.view; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.incendo.interfaces.core.Interface; 5 | import org.incendo.interfaces.core.arguments.InterfaceArguments; 6 | import org.incendo.interfaces.core.pane.Pane; 7 | import org.incendo.interfaces.core.transform.InterruptUpdateException; 8 | 9 | /** 10 | * Represents a currently open interface (a "view"). 11 | * 12 | * @param the type of pane this view can view 13 | * @param the viewer type 14 | */ 15 | public interface InterfaceView { 16 | 17 | /** 18 | * Returns the parent interface. 19 | * 20 | * @return the interface 21 | */ 22 | @NonNull Interface backing(); 23 | 24 | /** 25 | * Returns the argument provided to this view. 26 | * 27 | * @return the view's argument 28 | */ 29 | @NonNull InterfaceArguments arguments(); 30 | 31 | /** 32 | * Returns the viewer of this view. 33 | *

34 | * This will always return a value; every view must be constructed 35 | * with a viewer. This does not mean that the viewer is currently viewing 36 | * this view. 37 | * 38 | * @return the viewer 39 | * @see #viewing() check if the viewer is currently viewing this view 40 | */ 41 | @NonNull U viewer(); 42 | 43 | /** 44 | * Returns true if the viewer is currently viewing this view. False 45 | * if not. 46 | * 47 | * @return true if viewing, false if not 48 | */ 49 | boolean viewing(); 50 | 51 | /** 52 | * Returns the pane. 53 | * 54 | * @return the pane 55 | */ 56 | T pane(); 57 | 58 | /** 59 | * Opens the view to the viewer. 60 | * 61 | * @see #viewer() 62 | */ 63 | void open(); 64 | 65 | /** 66 | * Triggers a manual update. 67 | */ 68 | default void update() { 69 | if (this.viewing()) { 70 | try { 71 | this.backing().open(this.viewer(), this.arguments()); 72 | } catch (final InterruptUpdateException ignored) { 73 | } 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /examples/example-next/src/main/kotlin/org/incendo/interfaces/example/next/DelayedRequestExampleInterface.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.example.next 2 | 3 | import kotlinx.coroutines.DelicateCoroutinesApi 4 | import kotlinx.coroutines.GlobalScope 5 | import kotlinx.coroutines.delay 6 | import kotlinx.coroutines.launch 7 | import net.kyori.adventure.text.Component.text 8 | import org.bukkit.Material 9 | import org.incendo.interfaces.next.drawable.Drawable 10 | import org.incendo.interfaces.next.element.StaticElement 11 | import org.incendo.interfaces.next.interfaces.Interface 12 | import org.incendo.interfaces.next.interfaces.buildCombinedInterface 13 | import kotlin.time.Duration.Companion.seconds 14 | 15 | public class DelayedRequestExampleInterface : RegistrableInterface { 16 | private companion object { 17 | private val BACKING_ELEMENT = StaticElement(Drawable.drawable(Material.GRAY_CONCRETE)) 18 | } 19 | 20 | override val subcommand: String = "delayed" 21 | 22 | @OptIn(DelicateCoroutinesApi::class) 23 | override fun create(): Interface<*> = 24 | buildCombinedInterface { 25 | initialTitle = text(subcommand) 26 | rows = 2 27 | 28 | withTransform { pane, _ -> 29 | suspendingData().forEachIndexed { index, material -> 30 | pane[0, index] = StaticElement(Drawable.drawable(material)) 31 | } 32 | } 33 | 34 | withTransform { pane, _ -> 35 | for (index in 0..8) { 36 | pane[1, index] = BACKING_ELEMENT 37 | } 38 | 39 | pane[0, 8] = 40 | StaticElement(Drawable.drawable(Material.ENDER_PEARL)) { 41 | // This is very unsafe, it's up to you to set up a way to reliably 42 | // launch coroutines per player in a click handler. 43 | GlobalScope.launch { 44 | it.view.back() 45 | } 46 | } 47 | } 48 | } 49 | 50 | private suspend fun suspendingData(): List { 51 | delay(3.seconds) 52 | return listOf(Material.GREEN_CONCRETE, Material.YELLOW_CONCRETE, Material.RED_CONCRETE) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/interfaces/AbstractInterfaceBuilder.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.interfaces 2 | 3 | import org.bukkit.event.inventory.InventoryCloseEvent 4 | import org.bukkit.inventory.ItemStack 5 | import org.incendo.interfaces.next.click.ClickHandler 6 | import org.incendo.interfaces.next.pane.Pane 7 | import org.incendo.interfaces.next.properties.Trigger 8 | import org.incendo.interfaces.next.transform.AppliedTransform 9 | import org.incendo.interfaces.next.transform.ReactiveTransform 10 | import org.incendo.interfaces.next.transform.Transform 11 | import org.incendo.interfaces.next.utilities.IncrementingInteger 12 | 13 | public abstract class AbstractInterfaceBuilder

> internal constructor() : InterfaceBuilder() { 14 | private companion object { 15 | private val DEFAULT_REASONS = 16 | InventoryCloseEvent.Reason 17 | .values() 18 | .toList() 19 | .minus(InventoryCloseEvent.Reason.PLUGIN) 20 | } 21 | 22 | private val transformCounter by IncrementingInteger() 23 | 24 | protected val closeHandlers: MutableMap = mutableMapOf() 25 | protected val transforms: MutableCollection> = mutableListOf() 26 | protected val clickPreprocessors: MutableCollection = mutableListOf() 27 | 28 | public var itemPostProcessor: ((ItemStack) -> Unit)? = null 29 | 30 | public fun withTransform( 31 | vararg triggers: Trigger, 32 | transform: Transform

, 33 | ) { 34 | transforms.add(AppliedTransform(transformCounter, triggers.toSet(), transform)) 35 | } 36 | 37 | public fun addTransform(reactiveTransform: ReactiveTransform

) { 38 | transforms.add(AppliedTransform(transformCounter, reactiveTransform.triggers.toSet(), reactiveTransform)) 39 | } 40 | 41 | public fun withCloseHandler( 42 | reasons: Collection = DEFAULT_REASONS, 43 | closeHandler: CloseHandler, 44 | ) { 45 | reasons.forEach { 46 | closeHandlers[it] = closeHandler 47 | } 48 | } 49 | 50 | public fun withPreprocessor(handler: ClickHandler) { 51 | clickPreprocessors += handler 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /kotlin/src/main/kotlin/org/incendo/interfaces/kotlin/paper/MutableChestPaneView.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.kotlin.paper 2 | 3 | import org.incendo.interfaces.core.element.Element 4 | import org.incendo.interfaces.core.pane.Pane 5 | import org.incendo.interfaces.core.util.Vector2 6 | import org.incendo.interfaces.paper.element.ItemStackElement 7 | import org.incendo.interfaces.paper.pane.ChestPane 8 | import org.incendo.interfaces.paper.view.ChestView 9 | import org.incendo.interfaces.paper.view.PlayerView 10 | import org.incendo.interfaces.paper.view.TaskableView 11 | 12 | public data class MutableChestPaneView( 13 | private var internalPane: ChestPane, 14 | private val view: ChestView, 15 | ) : Pane, 16 | PlayerView by view, 17 | TaskableView by view { 18 | override fun elements(): MutableCollection = internalPane.elements() 19 | 20 | /** 21 | * Returns the element at the given position. 22 | * 23 | * @param x the x coordinate 24 | * @param y the y coordinate 25 | * @return the element 26 | */ 27 | public operator fun get( 28 | x: Int, 29 | y: Int, 30 | ): ItemStackElement = internalPane.element(x, y) 31 | 32 | /** 33 | * Sets an element at the given position. 34 | * 35 | * @param x the x coordinate 36 | * @param y the y coordinate 37 | * @param element the element 38 | */ 39 | public operator fun set( 40 | x: Int, 41 | y: Int, 42 | element: ItemStackElement, 43 | ): Unit = 44 | mutate { 45 | element(element, x, y) 46 | } 47 | 48 | /** 49 | * Sets an element at the given position. 50 | * 51 | * @param position the vector coordinate 52 | * @param element the element 53 | */ 54 | public operator fun set( 55 | position: Vector2, 56 | element: ItemStackElement, 57 | ): Unit = 58 | mutate { 59 | element(element, position.x, position.y) 60 | } 61 | 62 | private fun mutate(mutator: ChestPane.() -> ChestPane) { 63 | this.internalPane = internalPane.mutator() 64 | } 65 | 66 | /** 67 | * Converts this mutable chest pane to a [ChestPane]. 68 | * 69 | * @return the converted pane 70 | */ 71 | public fun toChestPane(): ChestPane = internalPane 72 | } 73 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/transform/InterfacePropertyImpl.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.transform; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | 5 | import java.lang.ref.WeakReference; 6 | import java.util.Collection; 7 | import java.util.Objects; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | 10 | class InterfacePropertyImpl implements InterfaceProperty { 11 | 12 | private final Collection, TriConsumer>> updateListeners = 13 | ConcurrentHashMap.newKeySet(); 14 | private T value; 15 | 16 | InterfacePropertyImpl(final T value) { 17 | this.value = value; 18 | } 19 | 20 | @Override 21 | public T get() { 22 | return this.value; 23 | } 24 | 25 | @Override 26 | public void set(final T value) { 27 | T oldValue = this.value; 28 | this.value = value; 29 | 30 | var iterator = this.updateListeners.iterator(); 31 | while (iterator.hasNext()) { 32 | // Check if the reference has been garbage collected or not 33 | final Pair, TriConsumer> pair = iterator.next(); 34 | var object = pair.getFirst().get(); 35 | if (object == null) { 36 | iterator.remove(); 37 | continue; 38 | } 39 | pair.getSecond().accept(object, oldValue, this.value); 40 | } 41 | } 42 | 43 | @Override 44 | public void addListener(final O reference, @NonNull final TriConsumer consumer) { 45 | this.updateListeners.removeIf(f -> f.getFirst().get() == null); 46 | this.updateListeners.add( 47 | new Pair<>( 48 | new WeakReference<>(reference), 49 | (TriConsumer) consumer 50 | ) 51 | ); 52 | } 53 | 54 | @Override 55 | public boolean equals(final Object o) { 56 | if (this == o) { 57 | return true; 58 | } 59 | if (o == null || getClass() != o.getClass()) { 60 | return false; 61 | } 62 | final InterfacePropertyImpl that = (InterfacePropertyImpl) o; 63 | return Objects.equals(this.value, that.value); 64 | } 65 | 66 | @Override 67 | public int hashCode() { 68 | return Objects.hash(this.value); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/view/PlayerView.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.view; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import org.bukkit.Bukkit; 5 | import org.bukkit.inventory.InventoryHolder; 6 | import org.checkerframework.checker.nullness.qual.NonNull; 7 | import org.incendo.interfaces.core.Interface; 8 | import org.incendo.interfaces.core.arguments.HashMapInterfaceArguments; 9 | import org.incendo.interfaces.core.arguments.InterfaceArguments; 10 | import org.incendo.interfaces.core.pane.Pane; 11 | import org.incendo.interfaces.core.view.InterfaceView; 12 | import org.incendo.interfaces.paper.PlayerViewer; 13 | import org.incendo.interfaces.paper.type.ChildTitledInterface; 14 | 15 | /** 16 | * An InterfaceView containing a Bukkit inventory. 17 | * 18 | * @param the pane type 19 | */ 20 | public interface PlayerView extends InterfaceView, 21 | InventoryHolder { 22 | 23 | /** 24 | * Opens a child interface. 25 | * 26 | * @param backing the backing interface 27 | * @param argument the argument 28 | * @param the type of view 29 | * @return the view 30 | */ 31 | @NonNull > C openChild( 32 | @NonNull Interface backing, 33 | @NonNull InterfaceArguments argument 34 | ); 35 | 36 | /** 37 | * Opens a titled child interface. 38 | * 39 | * @param backing the backing interface 40 | * @param argument the argument 41 | * @param title the title to open the interface with 42 | * @param the type of view 43 | * @return the view 44 | */ 45 | @NonNull > C openChild( 46 | @NonNull ChildTitledInterface backing, 47 | @NonNull InterfaceArguments argument, 48 | @NonNull Component title 49 | ); 50 | 51 | /** 52 | * Opens a child interface. 53 | * 54 | * @param backing the backing interface 55 | * @param the type of view 56 | * @return the view 57 | */ 58 | 59 | default @NonNull > C openChild(final @NonNull Interface backing) { 60 | return this.openChild(backing, HashMapInterfaceArguments.empty()); 61 | } 62 | 63 | /** 64 | * Emits a {@link ViewOpenEvent} for this view 65 | */ 66 | default void emitEvent() { 67 | Bukkit.getPluginManager().callEvent(new ViewOpenEvent(this)); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/transform/TransformContext.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.transform; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.incendo.interfaces.core.pane.Pane; 5 | import org.incendo.interfaces.core.view.InterfaceViewer; 6 | 7 | import java.util.Arrays; 8 | import java.util.Collection; 9 | 10 | /** 11 | * @param the type of the pane that this transform is applied to 12 | * @param the type of the viewer 13 | */ 14 | public final class TransformContext { 15 | 16 | private final int priority; 17 | private final Transform transform; 18 | private final Collection> properties; 19 | 20 | private TransformContext( 21 | final int priority, 22 | final @NonNull Transform transform, 23 | final @NonNull InterfaceProperty[] properties 24 | ) { 25 | this.properties = Arrays.asList(properties); 26 | this.priority = priority; 27 | this.transform = transform; 28 | } 29 | 30 | /** 31 | * Creates a new transform context instance 32 | * 33 | * @param priority the priority 34 | * @param transform the transform 35 | * @param properties the properties 36 | * @param the type of the pane that this transform is applied to 37 | * @param the type of the viewer 38 | * @return the context 39 | */ 40 | public static @NonNull TransformContext of( 41 | final int priority, 42 | final @NonNull Transform transform, 43 | final @NonNull InterfaceProperty... properties 44 | ) { 45 | return new TransformContext<>(priority, transform, properties); 46 | } 47 | 48 | /** 49 | * Returns the priority in which the transformation should be applied 50 | * 51 | * @return the priority 52 | */ 53 | public int priority() { 54 | return this.priority; 55 | } 56 | 57 | /** 58 | * Returns the transform of this context 59 | * 60 | * @return the transform 61 | */ 62 | public @NonNull Transform transform() { 63 | return this.transform; 64 | } 65 | 66 | /** 67 | * Returns the interface property of this context 68 | * 69 | * @return the property 70 | */ 71 | public @NonNull Collection<@NonNull InterfaceProperty> properties() { 72 | return this.properties; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /examples/example-next/src/main/kotlin/org/incendo/interfaces/example/next/TabbedExampleInterface.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.example.next 2 | 3 | import kotlinx.coroutines.delay 4 | import kotlinx.coroutines.runBlocking 5 | import org.bukkit.Material 6 | import org.incendo.interfaces.next.drawable.Drawable.Companion.drawable 7 | import org.incendo.interfaces.next.element.StaticElement 8 | import org.incendo.interfaces.next.interfaces.CombinedInterfaceBuilder 9 | import org.incendo.interfaces.next.interfaces.Interface 10 | import org.incendo.interfaces.next.interfaces.buildCombinedInterface 11 | import kotlin.time.Duration.Companion.milliseconds 12 | 13 | public class TabbedExampleInterface : RegistrableInterface { 14 | private companion object { 15 | private val ELEMENT = 16 | StaticElement( 17 | drawable(Material.NETHER_STAR), 18 | ) 19 | } 20 | 21 | override val subcommand: String = "tabbed" 22 | 23 | override fun create(): Interface<*> = first 24 | 25 | private fun CombinedInterfaceBuilder.defaults() { 26 | rows = 6 27 | 28 | withTransform { pane, _ -> 29 | pane[8, 2] = 30 | StaticElement(drawable(Material.IRON_INGOT)) { (player) -> 31 | completingLater = true 32 | 33 | runBlocking { 34 | first.open(player) 35 | complete() 36 | } 37 | } 38 | 39 | pane[8, 4] = 40 | StaticElement(drawable(Material.GOLD_INGOT)) { (player) -> 41 | completingLater = true 42 | 43 | runBlocking { 44 | second.open(player) 45 | complete() 46 | } 47 | } 48 | } 49 | } 50 | 51 | private val first = 52 | buildCombinedInterface { 53 | defaults() 54 | 55 | withTransform { pane, _ -> 56 | pane[0, 1] = ELEMENT 57 | } 58 | 59 | withTransform { pane, _ -> 60 | delay(100.milliseconds) 61 | pane[2, 4] = ELEMENT 62 | } 63 | } 64 | 65 | private val second = 66 | buildCombinedInterface { 67 | defaults() 68 | 69 | withTransform { pane, _ -> 70 | pane[1, 2] = ELEMENT 71 | } 72 | 73 | withTransform { pane, _ -> 74 | delay(100.milliseconds) 75 | pane[3, 5] = ELEMENT 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/click/clicks/Clicks.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.click.clicks; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.incendo.interfaces.core.click.Click; 5 | 6 | @SuppressWarnings("unused") 7 | public final class Clicks { 8 | 9 | private Clicks() { 10 | } 11 | 12 | /** 13 | * Returns a new left-click instance 14 | * 15 | * @param cause the click cause 16 | * @param shift whether or not the click was a shift-click 17 | * @param interact whether or nor the click was triggered by an interact event 18 | * @param the cause type 19 | * @return the click 20 | */ 21 | public static @NonNull Click leftClick( 22 | final @NonNull T cause, 23 | final boolean shift, 24 | final boolean interact 25 | ) { 26 | return new LeftClick<>( 27 | cause, 28 | shift, 29 | interact 30 | ); 31 | } 32 | 33 | /** 34 | * Returns a new middle-click instance 35 | * 36 | * @param cause the click cause 37 | * @param shift whether or not the click was a shift-click 38 | * @param the cause type 39 | * @return the click 40 | */ 41 | public static @NonNull Click middleClick( 42 | final @NonNull T cause, 43 | final boolean shift 44 | ) { 45 | return new MiddleClick<>( 46 | cause, 47 | shift 48 | ); 49 | } 50 | 51 | /** 52 | * Returns a new right-click instance 53 | * 54 | * @param cause the click cause 55 | * @param shift whether or not the click was a shift-click 56 | * @param interact whether or nor the click was triggered by an interact event 57 | * @param the cause type 58 | * @return the click 59 | */ 60 | public static @NonNull Click rightClick( 61 | final @NonNull T cause, 62 | final boolean shift, 63 | final boolean interact 64 | ) { 65 | return new RightClick<>( 66 | cause, 67 | shift, 68 | interact 69 | ); 70 | } 71 | 72 | /** 73 | * Returns a new click for an unknown click type 74 | * 75 | * @param cause the click cause 76 | * @param the cause type 77 | * @return the click 78 | */ 79 | public static @NonNull Click unknownClick( 80 | final @NonNull T cause 81 | ) { 82 | return new UnknownClick<>( 83 | cause 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/util/Vector2.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.util; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | 5 | import java.util.Objects; 6 | 7 | /** 8 | * A 2-dimensional vector storing integer components. 9 | */ 10 | public final class Vector2 { 11 | 12 | private final int x; 13 | private final int y; 14 | 15 | Vector2( 16 | final int x, 17 | final int y 18 | ) { 19 | this.x = x; 20 | this.y = y; 21 | } 22 | 23 | /** 24 | * Returns a new vector containing the given components 25 | * 26 | * @param x x component 27 | * @param y y component 28 | * @return the vector 29 | */ 30 | public static @NonNull Vector2 at( 31 | final int x, 32 | final int y 33 | ) { 34 | return new Vector2(x, y); 35 | } 36 | 37 | /** 38 | * Returns the x component 39 | * 40 | * @return x component 41 | */ 42 | public int x() { 43 | return this.x; 44 | } 45 | 46 | /** 47 | * Returns the y component 48 | * 49 | * @return y component 50 | */ 51 | public int y() { 52 | return this.y; 53 | } 54 | 55 | @Override 56 | public boolean equals(final Object o) { 57 | if (this == o) { 58 | return true; 59 | } 60 | if (o == null || getClass() != o.getClass()) { 61 | return false; 62 | } 63 | final Vector2 vector2 = (Vector2) o; 64 | return this.x() == vector2.x() && this.y() == vector2.y(); 65 | } 66 | 67 | @Override 68 | public int hashCode() { 69 | return Objects.hash(this.x(), this.y()); 70 | } 71 | 72 | // 73 | /** 74 | * Returns the x component 75 | * 76 | * @return x component 77 | */ 78 | public int getX() { 79 | return this.x(); 80 | } 81 | 82 | /** 83 | * Returns the y component 84 | * 85 | * @return y component 86 | */ 87 | public int getY() { 88 | return this.y(); 89 | } 90 | 91 | /** 92 | * Returns the x component 93 | * 94 | * @return x component 95 | */ 96 | public int component1() { 97 | return this.x(); 98 | } 99 | 100 | /** 101 | * Returns the y component 102 | * 103 | * @return y component 104 | */ 105 | public int component2() { 106 | return this.y(); 107 | } 108 | // 109 | 110 | } 111 | -------------------------------------------------------------------------------- /kotlin/src/main/kotlin/org/incendo/interfaces/kotlin/paper/MutableCombinedPaneView.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.kotlin.paper 2 | 3 | import org.incendo.interfaces.core.element.Element 4 | import org.incendo.interfaces.core.pane.Pane 5 | import org.incendo.interfaces.core.util.Vector2 6 | import org.incendo.interfaces.paper.element.ItemStackElement 7 | import org.incendo.interfaces.paper.pane.CombinedPane 8 | import org.incendo.interfaces.paper.view.CombinedView 9 | import org.incendo.interfaces.paper.view.PlayerView 10 | import org.incendo.interfaces.paper.view.TaskableView 11 | 12 | public data class MutableCombinedPaneView( 13 | private var internalPane: CombinedPane, 14 | private val view: CombinedView, 15 | ) : Pane, 16 | PlayerView by view, 17 | TaskableView by view { 18 | override fun elements(): MutableCollection = internalPane.elements() 19 | 20 | /** 21 | * Returns the element at the given position. 22 | * 23 | * @param x the x coordinate 24 | * @param y the y coordinate 25 | * @return the element 26 | */ 27 | public operator fun get( 28 | x: Int, 29 | y: Int, 30 | ): ItemStackElement = internalPane.element(x, y) 31 | 32 | /** 33 | * Sets an element at the given position. 34 | * 35 | * @param x the x coordinate 36 | * @param y the y coordinate 37 | * @param element the element 38 | */ 39 | public operator fun set( 40 | x: Int, 41 | y: Int, 42 | element: ItemStackElement, 43 | ): Unit = 44 | mutate { 45 | element(element, x, y) 46 | } 47 | 48 | /** 49 | * Sets an element at the given position. 50 | * 51 | * @param position the vector coordinate 52 | * @param element the element 53 | */ 54 | public operator fun set( 55 | position: Vector2, 56 | element: ItemStackElement, 57 | ): Unit = 58 | mutate { 59 | element(element, position.x, position.y) 60 | } 61 | 62 | public fun hotbar(x: Int): ItemStackElement = internalPane.hotbar(x) 63 | 64 | public fun hotbar( 65 | x: Int, 66 | element: ItemStackElement, 67 | ): Unit = 68 | mutate { 69 | hotbar(element, x) 70 | } 71 | 72 | private fun mutate(mutator: CombinedPane.() -> CombinedPane) { 73 | this.internalPane = internalPane.mutator() 74 | } 75 | 76 | /** 77 | * Converts this mutable chest pane to a [CombinedPane]. 78 | * 79 | * @return the converted pane 80 | */ 81 | public fun toCombinedPane(): CombinedPane = internalPane 82 | } 83 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/click/ClickContext.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.click; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.incendo.interfaces.core.pane.Pane; 5 | import org.incendo.interfaces.core.view.InterfaceView; 6 | import org.incendo.interfaces.core.view.InterfaceViewer; 7 | 8 | /** 9 | * Represents the context of a click. 10 | * 11 | * @param the pane's type 12 | * @param the click cause 13 | * @param the viewer type 14 | */ 15 | @SuppressWarnings("unused") 16 | public interface ClickContext { 17 | 18 | /** 19 | * The click cause 20 | * 21 | * @return the cause 22 | */ 23 | @NonNull U cause(); 24 | 25 | /** 26 | * The status of the click. 27 | * 28 | * @return the status of the click 29 | */ 30 | @NonNull ClickStatus status(); 31 | 32 | /** 33 | * Sets the status of the click. 34 | * 35 | * @param status the status 36 | */ 37 | void status(@NonNull ClickStatus status); 38 | 39 | /** 40 | * Returns true if this context is cancelling the click. 41 | * 42 | * @return true if the click is cancelled, false if not 43 | */ 44 | default boolean cancelled() { 45 | return this.status() == ClickStatus.DENY; 46 | } 47 | 48 | /** 49 | * Sets the click to cancelled if {@code cancelled} is true. 50 | * 51 | * @param cancelled if true, will cancel the click 52 | */ 53 | default void cancel(boolean cancelled) { 54 | if (cancelled) { 55 | this.status(ClickStatus.DENY); 56 | } else { 57 | this.status(ClickStatus.ALLOW); 58 | } 59 | } 60 | 61 | /** 62 | * Returns the view associated with this context. 63 | * 64 | * @return the view 65 | */ 66 | @NonNull InterfaceView view(); 67 | 68 | /** 69 | * Returns the viewer (the one who clicked). 70 | * 71 | * @return the viewer 72 | */ 73 | @NonNull V viewer(); 74 | 75 | /** 76 | * Returns the click associated with this context. 77 | * 78 | * @return the click 79 | */ 80 | @NonNull Click click(); 81 | 82 | /** 83 | * The status of a click event. 84 | */ 85 | enum ClickStatus { 86 | /** The click is allowed and the triggering event won't be cancelled. */ 87 | ALLOW, 88 | /** 89 | * The click status will be inherited from any previous click handlers. 90 | * If there are no previous click handlers, the default behavior is to allow the click. 91 | */ 92 | INHERIT, 93 | /** The click is denied and the triggering event will be cancelled. */ 94 | DENY 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /next/src/main/kotlin/org/incendo/interfaces/next/view/PlayerInterfaceView.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.next.view 2 | 3 | import net.kyori.adventure.text.Component 4 | import org.bukkit.entity.Player 5 | import org.bukkit.event.inventory.InventoryType 6 | import org.incendo.interfaces.next.InterfacesListeners 7 | import org.incendo.interfaces.next.interfaces.PlayerInterface 8 | import org.incendo.interfaces.next.inventory.PlayerInterfacesInventory 9 | import org.incendo.interfaces.next.pane.PlayerPane 10 | import org.incendo.interfaces.next.utilities.runSync 11 | 12 | public class PlayerInterfaceView internal constructor( 13 | player: Player, 14 | backing: PlayerInterface, 15 | ) : AbstractInterfaceView( 16 | player, 17 | backing, 18 | // todo(josh): should player interface views hold a parent? 19 | null, 20 | ) { 21 | override fun title(value: Component) { 22 | error("PlayerInventoryView's cannot have a title") 23 | } 24 | 25 | override fun createInventory(): PlayerInterfacesInventory = PlayerInterfacesInventory(player) 26 | 27 | override fun openInventory() { 28 | // Close whatever inventory the player has open so they can look at their normal inventory! 29 | // This will only continue if the menu hasn't been closed yet. 30 | if (!isOpen(player)) { 31 | // First we close then we set the interface so we don't double open! 32 | InterfacesListeners.instance.setOpenInterface(player.uniqueId, null) 33 | player.closeInventory() 34 | InterfacesListeners.instance.setOpenInterface(player.uniqueId, this) 35 | } 36 | 37 | // Double-check that this inventory is open now! 38 | if (isOpen(player)) { 39 | // Clear the player's inventory! 40 | player.inventory.clear() 41 | if (player.openInventory.topInventory.type == InventoryType.CRAFTING) { 42 | player.openInventory.topInventory.clear() 43 | } 44 | player.openInventory.setCursor(null) 45 | 46 | // Trigger onOpen manually because there is no real inventory being opened, 47 | // this will also re-draw the player inventory parts! 48 | onOpen() 49 | } 50 | } 51 | 52 | override fun close() { 53 | // Ensure we update the interface state in the main thread! 54 | // Even if the menu is not currently on the screen. 55 | runSync { 56 | InterfacesListeners.instance.setOpenInterface(player.uniqueId, null) 57 | } 58 | } 59 | 60 | override fun isOpen(player: Player): Boolean = 61 | player.openInventory.type == InventoryType.CRAFTING && 62 | InterfacesListeners.instance.getOpenInterface(player.uniqueId) == this 63 | } 64 | -------------------------------------------------------------------------------- /core/src/main/java/org/incendo/interfaces/core/click/ClickHandler.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.core.click; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.incendo.interfaces.core.pane.Pane; 5 | import org.incendo.interfaces.core.view.InterfaceViewer; 6 | 7 | import java.util.function.Consumer; 8 | 9 | /** 10 | * A function that handles a click event on an interface. 11 | * 12 | * @param the context type 13 | * @param the click cause 14 | * @param the viewer type 15 | * @param the context type 16 | */ 17 | public interface ClickHandler> extends Consumer { 18 | 19 | /** 20 | * Returns a {@code ClickHandler} that cancels the event and then calls 21 | * the given click handler. 22 | * 23 | * @param clickHandler the handler 24 | * @param the context type 25 | * @param the click cause 26 | * @param the viewer type 27 | * @param the context type 28 | * @return the handler 29 | */ 30 | static @NonNull > ClickHandler canceling( 32 | final @NonNull ClickHandler clickHandler 33 | ) { 34 | return (ctx) -> { 35 | ctx.status(ClickContext.ClickStatus.DENY); 36 | clickHandler.accept(ctx); 37 | }; 38 | } 39 | 40 | /** 41 | * Returns a {@code ClickHandler} that cancels the event. 42 | * 43 | * @param the context type 44 | * @param the click cause 45 | * @param the viewer type 46 | * @param the context type 47 | * @return the handler 48 | */ 49 | static @NonNull > ClickHandler cancel() { 51 | return (ctx) -> ctx.status(ClickContext.ClickStatus.DENY); 52 | } 53 | 54 | /** 55 | * Returns a {@code ClickHandler} that does nothing. 56 | * 57 | * @param the context type 58 | * @param the click cause 59 | * @param the viewer type 60 | * @param the context type 61 | * @return the handler 62 | */ 63 | static @NonNull > ClickHandler dummy() { 65 | return ctx -> {}; 66 | } 67 | 68 | /** 69 | * Returns a new {@link ClickHandler} that first executes the current handler, and then 70 | * executes the other handler. 71 | * 72 | * @param other the other handler 73 | * @return a combination of this and the given click handler 74 | */ 75 | default @NonNull ClickHandler andThen(final @NonNull ClickHandler other) { 76 | final ClickHandler current = this; 77 | return (w) -> { 78 | current.accept(w); 79 | other.accept(w); 80 | }; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/utils/Components.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.utils; 2 | 3 | import com.google.gson.JsonElement; 4 | 5 | import java.lang.reflect.Method; 6 | 7 | import net.kyori.adventure.text.Component; 8 | import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; 9 | import org.checkerframework.checker.nullness.qual.NonNull; 10 | import org.checkerframework.checker.nullness.qual.Nullable; 11 | import org.checkerframework.framework.qual.DefaultQualifier; 12 | 13 | @DefaultQualifier(NonNull.class) 14 | public final class Components { 15 | 16 | private static final @Nullable Object NATIVE_GSON; 17 | private static final @Nullable Method NATIVE_DESERIALIZE; 18 | private static final String NOT_RELOCATED_PREFIX = String.join(".", "net", "kyori"); 19 | 20 | static { 21 | if (!PaperUtils.isPaper()) { 22 | NATIVE_GSON = null; 23 | NATIVE_DESERIALIZE = null; 24 | } else { 25 | try { 26 | final Class gsonClass = Class.forName(String.join( 27 | ".", 28 | "net", 29 | "kyori", 30 | "adventure", 31 | "text", 32 | "serializer", 33 | "gson", 34 | "GsonComponentSerializer" 35 | )); 36 | NATIVE_DESERIALIZE = gsonClass.getMethod("deserializeFromTree", JsonElement.class); 37 | NATIVE_GSON = gsonClass.getDeclaredMethod("gson").invoke(null); 38 | } catch (final ReflectiveOperationException ex) { 39 | throw new RuntimeException(ex); 40 | } 41 | } 42 | } 43 | 44 | private Components() { 45 | } 46 | 47 | /** 48 | * Convert the component to the native adventure type. 49 | * 50 | * @param component component 51 | * @return native adventure component 52 | */ 53 | public static Object toNative(final Component component) { 54 | if (NATIVE_GSON == null || NATIVE_DESERIALIZE == null) { 55 | throw new IllegalStateException(); 56 | } 57 | if (component.getClass().getName().startsWith(NOT_RELOCATED_PREFIX)) { 58 | return component; 59 | } 60 | try { 61 | return NATIVE_DESERIALIZE.invoke(NATIVE_GSON, GsonComponentSerializer.gson().serializeToTree(component)); 62 | } catch (final ReflectiveOperationException ex) { 63 | throw new RuntimeException(ex); 64 | } 65 | } 66 | 67 | /** 68 | * Get the class object for the platform's native adventure. 69 | * 70 | * @return class 71 | */ 72 | public static Class nativeAdventureComponentClass() { 73 | try { 74 | return Class.forName(String.join(".", "net", "kyori", "adventure", "text", "Component")); 75 | } catch (final ClassNotFoundException ex) { 76 | throw new RuntimeException(ex); 77 | } 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/pane/BookPane.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.pane; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.incendo.interfaces.core.element.Element; 5 | import org.incendo.interfaces.core.pane.Pane; 6 | import org.incendo.interfaces.paper.element.TextElement; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Collection; 10 | import java.util.List; 11 | 12 | /** 13 | * A pane based off of a Minecraft book. 14 | */ 15 | @SuppressWarnings("unused") 16 | public class BookPane implements Pane { 17 | 18 | private final @NonNull List pages; 19 | 20 | /** 21 | * Constructs {@code BookPane}. 22 | */ 23 | public BookPane() { 24 | this.pages = new ArrayList<>(); 25 | } 26 | 27 | /** 28 | * Constructs {@code BookPane}. 29 | * 30 | * @param pages the pages 31 | */ 32 | public BookPane(final @NonNull List pages) { 33 | this.pages = pages; 34 | } 35 | 36 | /** 37 | * Returns the amount of lines in the pane. 38 | * 39 | * @return the amount of lines in the pane 40 | */ 41 | public int count() { 42 | return this.pages.size(); 43 | } 44 | 45 | /** 46 | * Adds a page. 47 | * 48 | * @param element the element 49 | * @return a new {@code BookPane} 50 | */ 51 | public @NonNull BookPane add(final @NonNull TextElement element) { 52 | final @NonNull List pages = new ArrayList<>(this.pages); 53 | pages.add(element); 54 | return new BookPane(pages); 55 | } 56 | 57 | /** 58 | * Inserts a page at the index. 59 | * 60 | * @param index the index 61 | * @param element the element 62 | * @return a new {@code BookPane} 63 | * @throws IndexOutOfBoundsException if index is greater than the pane size or lesser than 0 64 | */ 65 | public @NonNull BookPane add(final int index, final @NonNull TextElement element) { 66 | final @NonNull List pages = new ArrayList<>(this.pages); 67 | pages.add(index, element); 68 | return new BookPane(pages); 69 | } 70 | 71 | /** 72 | * Removes a page at the given index. 73 | * 74 | * @param index the index 75 | * @return a new {@code BookPane} 76 | * @throws IndexOutOfBoundsException if index is greater than the pane size or lesser than 0 77 | */ 78 | public @NonNull BookPane remove(final int index) { 79 | final @NonNull List pages = new ArrayList<>(this.pages); 80 | pages.remove(index); 81 | return new BookPane(pages); 82 | } 83 | 84 | /** 85 | * Returns the list of text elements. 86 | * 87 | * @return the text elements 88 | */ 89 | @Override 90 | public @NonNull Collection elements() { 91 | return List.copyOf(this.pages); 92 | } 93 | 94 | /** 95 | * Returns the list of pages. 96 | * 97 | * @return the list of pages 98 | */ 99 | public @NonNull List pages() { 100 | return List.copyOf(this.pages); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/transform/PaperTransform.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.transform; 2 | 3 | 4 | import java.util.function.Supplier; 5 | 6 | import net.kyori.adventure.text.Component; 7 | import org.checkerframework.checker.nullness.qual.NonNull; 8 | import org.incendo.interfaces.core.transform.Transform; 9 | import org.incendo.interfaces.paper.PlayerViewer; 10 | import org.incendo.interfaces.paper.element.ItemStackElement; 11 | import org.incendo.interfaces.paper.element.TextElement; 12 | import org.incendo.interfaces.paper.pane.BookPane; 13 | import org.incendo.interfaces.paper.pane.ChestPane; 14 | 15 | /** 16 | * Utility methods for Paper transformations. 17 | */ 18 | @SuppressWarnings("unused") 19 | public interface PaperTransform { 20 | 21 | /** 22 | * Returns a {@link ChestPane} {@link Transform} that fills the pane with an ItemStack. 23 | * 24 | * @param element the element 25 | * @return the transform 26 | */ 27 | static @NonNull Transform chestFill(final @NonNull ItemStackElement element) { 28 | return (pane, view) -> { 29 | final int length = ChestPane.MINECRAFT_CHEST_WIDTH; 30 | final int height = pane.rows(); 31 | 32 | for (int x = 0; x < length; x++) { 33 | for (int y = 0; y < height; y++) { 34 | pane = pane.element(element, x, y); 35 | } 36 | } 37 | 38 | return pane; 39 | }; 40 | } 41 | 42 | /** 43 | * Returns a {@link ChestPane} {@link Transform} that adds an {@link ItemStackElement} to the pane 44 | * at the specified coordinates. The supplier allows the displayed element to be updated with the view. 45 | * 46 | * @param element the element supplier 47 | * @param x the x coordinate 48 | * @param y the y coordinate 49 | * @return the transform 50 | */ 51 | static @NonNull Transform chestItem( 52 | final @NonNull Supplier<@NonNull ItemStackElement> element, 53 | final int x, 54 | final int y 55 | ) { 56 | return (pane, view) -> pane.element(element.get(), x, y); 57 | } 58 | 59 | /** 60 | * Returns a {@link ChestPane} {@link Transform} that adds an {@link ItemStackElement} to the pane 61 | * at the specified coordinates. 62 | * 63 | * @param element the element 64 | * @param x the x coordinate 65 | * @param y the y coordinate 66 | * @return the transform 67 | */ 68 | static @NonNull Transform chestItem( 69 | final @NonNull ItemStackElement element, 70 | final int x, 71 | final int y 72 | ) { 73 | return chestItem(() -> element, x, y); 74 | } 75 | 76 | /** 77 | * Returns a {@link Transform} that adds text to the pane. 78 | * 79 | * @param pages the pages as components 80 | * @return the transform 81 | */ 82 | static @NonNull Transform bookText(final @NonNull Component... pages) { 83 | return (pane, view) -> { 84 | @NonNull BookPane bookPane = pane; 85 | 86 | for (final @NonNull Component page : pages) { 87 | bookPane = bookPane.add(TextElement.of(page)); 88 | } 89 | 90 | return bookPane; 91 | }; 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/PlayerViewerImpl.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.plugin.Plugin; 6 | import org.bukkit.plugin.java.JavaPlugin; 7 | import org.checkerframework.checker.nullness.qual.NonNull; 8 | import org.incendo.interfaces.core.view.InterfaceView; 9 | import org.incendo.interfaces.paper.utils.PaperUtils; 10 | import org.incendo.interfaces.paper.view.BookView; 11 | import org.incendo.interfaces.paper.view.ChestView; 12 | import org.incendo.interfaces.paper.view.CombinedView; 13 | import org.incendo.interfaces.paper.view.PlayerInventoryView; 14 | 15 | import java.util.Objects; 16 | 17 | final class PlayerViewerImpl implements PlayerViewer { 18 | 19 | private final @NonNull Player player; 20 | 21 | /** 22 | * Constructs {@code PlayerViewer}. 23 | * 24 | * @param player the player 25 | */ 26 | PlayerViewerImpl(final @NonNull Player player) { 27 | this.player = player; 28 | } 29 | 30 | private void openChestView(final @NonNull ChestView chestView) { 31 | this.player.openInventory(chestView.getInventory()); 32 | } 33 | 34 | private void openCombinedView(final @NonNull CombinedView combinedView) { 35 | this.player.openInventory(combinedView.getInventory()); 36 | } 37 | 38 | private void openBookView(final @NonNull BookView bookView) { 39 | if (PaperUtils.isPaper()) { 40 | this.player.openBook(bookView.book()); 41 | } else { 42 | throw new UnsupportedOperationException("BookView is only implemented on Paper-based servers!"); 43 | } 44 | } 45 | 46 | private void openPlayerView(final @NonNull PlayerInventoryView inventoryView) { 47 | inventoryView.open(); 48 | } 49 | 50 | @Override 51 | public void open(final @NonNull InterfaceView view) { 52 | final Runnable runnable = () -> { 53 | if (view instanceof ChestView) { 54 | this.openChestView((ChestView) view); 55 | } else if (view instanceof CombinedView) { 56 | this.openCombinedView((CombinedView) view); 57 | } else if (view instanceof BookView) { 58 | this.openBookView((BookView) view); 59 | } else if (view instanceof PlayerInventoryView) { 60 | this.openPlayerView((PlayerInventoryView) view); 61 | } else { 62 | throw new UnsupportedOperationException("Cannot open view of type " + view.getClass().getName() + "."); 63 | } 64 | }; 65 | 66 | if (Bukkit.isPrimaryThread()) { 67 | runnable.run(); 68 | } else { 69 | try { 70 | Plugin plugin = Objects.requireNonNullElseGet( 71 | PaperInterfaceListeners.plugin(), 72 | () -> JavaPlugin.getProvidingPlugin(this.getClass()) 73 | ); 74 | 75 | Bukkit.getScheduler().callSyncMethod(plugin, () -> { 76 | runnable.run(); 77 | 78 | return null; 79 | }).get(); 80 | } catch (final Exception e) { 81 | throw new RuntimeException(e); 82 | } 83 | } 84 | } 85 | 86 | @Override 87 | public void close() { 88 | this.player.closeInventory(); 89 | } 90 | 91 | @Override 92 | public @NonNull Player player() { 93 | return this.player; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/utils/InventoryFactory.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.utils; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 5 | import net.kyori.adventure.translation.GlobalTranslator; 6 | import net.kyori.adventure.translation.Translator; 7 | import org.bukkit.Bukkit; 8 | import org.bukkit.Server; 9 | import org.bukkit.entity.Player; 10 | import org.bukkit.inventory.Inventory; 11 | import org.bukkit.inventory.InventoryHolder; 12 | import org.checkerframework.checker.nullness.qual.MonotonicNonNull; 13 | import org.checkerframework.checker.nullness.qual.NonNull; 14 | import org.checkerframework.framework.qual.DefaultQualifier; 15 | import org.jetbrains.annotations.Nullable; 16 | 17 | import java.lang.reflect.Method; 18 | import java.util.Locale; 19 | 20 | @DefaultQualifier(NonNull.class) 21 | public final class InventoryFactory { 22 | 23 | private static @MonotonicNonNull Method paperCreateInventory; 24 | 25 | private InventoryFactory() { 26 | } 27 | 28 | /** 29 | * Create an inventory with the appropriate method for the platform. 30 | * 31 | * @param viewer viewer 32 | * @param inventoryHolder holder 33 | * @param size size 34 | * @param title title 35 | * @return inventory 36 | */ 37 | public static Inventory createInventory( 38 | final Player viewer, 39 | final InventoryHolder inventoryHolder, 40 | final int size, 41 | final Component title 42 | ) { 43 | if (PaperUtils.isPaper()) { 44 | return paperCreateInventory(inventoryHolder, size, title); 45 | } 46 | return legacyCreateInventory(viewer, inventoryHolder, size, title); 47 | } 48 | 49 | @SuppressWarnings("deprecation") 50 | private static Inventory legacyCreateInventory( 51 | final Player viewer, 52 | final InventoryHolder inventoryHolder, 53 | final int size, 54 | final Component title 55 | ) { 56 | final @Nullable Locale locale = Translator.parseLocale(viewer.getLocale()); 57 | return Bukkit.getServer().createInventory( 58 | inventoryHolder, 59 | size, 60 | LegacyComponentSerializer.legacySection().serialize( 61 | GlobalTranslator.render(title, locale == null ? Locale.US : locale) 62 | ) 63 | ); 64 | } 65 | 66 | private static Inventory paperCreateInventory(final InventoryHolder inventoryHolder, final int size, final Component title) { 67 | try { 68 | return (Inventory) paperCreateInventoryMethod().invoke( 69 | Bukkit.getServer(), 70 | inventoryHolder, 71 | size, 72 | Components.toNative(title) 73 | ); 74 | } catch (final ReflectiveOperationException ex) { 75 | throw new RuntimeException(ex); 76 | } 77 | } 78 | 79 | private static Method paperCreateInventoryMethod() { 80 | if (paperCreateInventory == null) { 81 | try { 82 | paperCreateInventory = Server.class.getMethod( 83 | "createInventory", 84 | InventoryHolder.class, 85 | int.class, 86 | Components.nativeAdventureComponentClass() 87 | ); 88 | } catch (final ReflectiveOperationException ex) { 89 | throw new RuntimeException(ex); 90 | } 91 | } 92 | return paperCreateInventory; 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/view/BookView.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.view; 2 | 3 | import net.kyori.adventure.inventory.Book; 4 | import net.kyori.adventure.text.Component; 5 | import org.checkerframework.checker.nullness.qual.NonNull; 6 | import org.incendo.interfaces.core.arguments.InterfaceArguments; 7 | import org.incendo.interfaces.core.view.InterfaceView; 8 | import org.incendo.interfaces.paper.PlayerViewer; 9 | import org.incendo.interfaces.paper.element.TextElement; 10 | import org.incendo.interfaces.paper.pane.BookPane; 11 | import org.incendo.interfaces.paper.type.BookInterface; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * The view of a Book-based interface. 18 | */ 19 | public final class BookView implements InterfaceView { 20 | 21 | private final @NonNull BookInterface parent; 22 | private final @NonNull PlayerViewer viewer; 23 | private final @NonNull InterfaceArguments argument; 24 | private final @NonNull BookPane pane; 25 | private final @NonNull Book book; 26 | 27 | /** 28 | * Constructs {@code BookView}. 29 | * 30 | * @param parent the parent 31 | * @param viewer the viewer 32 | * @param argument the argument 33 | * @param title the title 34 | */ 35 | public BookView( 36 | final @NonNull BookInterface parent, 37 | final @NonNull PlayerViewer viewer, 38 | final @NonNull InterfaceArguments argument, 39 | final @NonNull Component title 40 | ) { 41 | this.parent = parent; 42 | this.viewer = viewer; 43 | this.argument = argument; 44 | 45 | @NonNull BookPane pane = new BookPane(); 46 | 47 | for (final var transform : this.parent.transformations()) { 48 | pane = transform.transform().apply(pane, this); 49 | } 50 | 51 | this.pane = pane; 52 | 53 | final @NonNull List pages = new ArrayList<>(); 54 | 55 | for (final @NonNull TextElement element : this.pane.pages()) { 56 | pages.add(element.text()); 57 | } 58 | 59 | this.book = Book.book(title, Component.empty(), pages); 60 | } 61 | 62 | /** 63 | * Always returns false. 64 | * 65 | * @return false 66 | */ 67 | @Override 68 | public boolean viewing() { 69 | // Not sure how to properly implement this, or even if it can be done reliably. Editing a book sends an event, 70 | // but just closing the book with the ESC key doesn't. 71 | return false; 72 | } 73 | 74 | /** 75 | * Returns the parent. 76 | * 77 | * @return the parent 78 | */ 79 | @Override 80 | public @NonNull BookInterface backing() { 81 | return this.parent; 82 | } 83 | 84 | /** 85 | * Returns the viewer. 86 | * 87 | * @return the viewer 88 | */ 89 | @Override 90 | public @NonNull PlayerViewer viewer() { 91 | return this.viewer; 92 | } 93 | 94 | /** 95 | * Returns the argument. 96 | * 97 | * @return the argument 98 | */ 99 | @Override 100 | public @NonNull InterfaceArguments arguments() { 101 | return this.argument; 102 | } 103 | 104 | /** 105 | * Opens the view to the viewer. 106 | */ 107 | @Override 108 | public void open() { 109 | this.viewer.open(this); 110 | } 111 | 112 | /** 113 | * Returns the pane. 114 | * 115 | * @return the pane 116 | */ 117 | @Override 118 | public @NonNull BookPane pane() { 119 | return this.pane; 120 | } 121 | 122 | /** 123 | * Returns the book. 124 | * 125 | * @return the book 126 | */ 127 | public @NonNull Book book() { 128 | return this.book; 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/pane/ChestPane.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.pane; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.incendo.interfaces.core.element.Element; 5 | import org.incendo.interfaces.core.pane.GridPane; 6 | import org.incendo.interfaces.core.util.Vector2; 7 | import org.incendo.interfaces.paper.element.ItemStackElement; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Collection; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | /** 16 | * A pane based off of a Minecraft chest inventory. 17 | */ 18 | public final class ChestPane implements GridPane> { 19 | 20 | public static final int MINECRAFT_CHEST_WIDTH = 9; 21 | 22 | private final @NonNull Map<@NonNull Vector2, @NonNull ItemStackElement> elements; 23 | 24 | private final int rows; 25 | 26 | /** 27 | * Constructs {@code ChestPane}. 28 | * 29 | * @param rows the amount of rows 30 | */ 31 | public ChestPane(final int rows) { 32 | this.rows = rows; 33 | 34 | this.elements = new HashMap<>(); 35 | 36 | // Fill the arrays with empty elements. 37 | for (int x = 0; x < MINECRAFT_CHEST_WIDTH; x++) { 38 | for (int y = 0; y < this.rows; y++) { 39 | this.elements.put(Vector2.at(x, y), ItemStackElement.empty()); 40 | } 41 | } 42 | } 43 | 44 | /** 45 | * Constructs {@code ChestPane}. 46 | * 47 | * @param rows amount of rows 48 | * @param elements the elements 49 | */ 50 | public ChestPane( 51 | final int rows, 52 | final @NonNull Map<@NonNull Vector2, @NonNull ItemStackElement> elements 53 | ) { 54 | this.rows = rows; 55 | this.elements = elements; 56 | } 57 | 58 | /** 59 | * Returns the elements of the chest as a 2d array. 60 | * 61 | * @return the elements 62 | */ 63 | public @NonNull Map<@NonNull Vector2, @NonNull ItemStackElement> chestElements() { 64 | return this.elements; 65 | } 66 | 67 | /** 68 | * Returns the amount of rows this pane has. 69 | * 70 | * @return the amount of rows 71 | */ 72 | public int rows() { 73 | return this.rows; 74 | } 75 | 76 | @Override 77 | public @NonNull Collection elements() { 78 | final @NonNull List tempElements = new ArrayList<>(); 79 | 80 | // Fill the temp elements list with the elements. 81 | for (int x = 0; x < MINECRAFT_CHEST_WIDTH; x++) { 82 | for (int y = 0; y < this.rows; y++) { 83 | tempElements.add(this.elements.get(Vector2.at(x, y))); 84 | } 85 | } 86 | 87 | return tempElements; 88 | } 89 | 90 | @Override 91 | public @NonNull ChestPane element( 92 | final @NonNull ItemStackElement element, 93 | final int x, 94 | final int y 95 | ) { 96 | if (y >= this.rows || x > 8) { 97 | throw new IllegalArgumentException("Cannot set element outside of the bounds of this chest pane."); 98 | } 99 | 100 | final Map<@NonNull Vector2, @NonNull ItemStackElement> newElements = new HashMap<>(this.elements); 101 | 102 | newElements.put(Vector2.at(x, y), element); 103 | 104 | return new ChestPane(this.rows, newElements); 105 | } 106 | 107 | @Override 108 | public @NonNull ItemStackElement element(final int x, final int y) { 109 | if (y >= this.rows || x > 8) { 110 | throw new IllegalArgumentException("Cannot access element outside of the bounds of this chest pane."); 111 | } 112 | 113 | return this.elements.get(Vector2.at(x, y)); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /kotlin/src/main/kotlin/org/incendo/interfaces/kotlin/paper/MutablePlayerPaneView.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.kotlin.paper 2 | 3 | import org.incendo.interfaces.core.element.Element 4 | import org.incendo.interfaces.core.pane.Pane 5 | import org.incendo.interfaces.core.util.Vector2 6 | import org.incendo.interfaces.paper.element.ItemStackElement 7 | import org.incendo.interfaces.paper.pane.PlayerPane 8 | import org.incendo.interfaces.paper.utils.PaperUtils 9 | import org.incendo.interfaces.paper.view.PlayerView 10 | 11 | public data class MutablePlayerPaneView( 12 | private var internalPane: PlayerPane, 13 | private val view: PlayerView, 14 | ) : Pane, 15 | PlayerView by view { 16 | override fun elements(): MutableCollection = internalPane.elements() 17 | 18 | /** Access to the hotbar slots. */ 19 | public val hotbar: ElementAccess = ElementAccess(PlayerPane.SlotType.HOTBAR) 20 | 21 | /** Access to the main slots. */ 22 | public val main: ElementAccess = ElementAccess(PlayerPane.SlotType.MAIN) 23 | 24 | /** Access to the armor slots. */ 25 | public val armor: ElementAccess = ElementAccess(PlayerPane.SlotType.ARMOR) 26 | 27 | /** Access to the off hand element. */ 28 | public var offHand: ItemStackElement 29 | get() = internalPane.offHand() 30 | set(value) = mutate { internalPane.offHand(value) } 31 | 32 | private fun mutate(mutator: PlayerPane.() -> PlayerPane) { 33 | this.internalPane = internalPane.mutator() 34 | } 35 | 36 | /** 37 | * Converts this mutable player pane to a [PlayerPane] 38 | * 39 | * @return the converted pane 40 | */ 41 | public fun toPlayerPane(): PlayerPane = internalPane 42 | 43 | public inner class ElementAccess 44 | internal constructor( 45 | private val slotType: PlayerPane.SlotType, 46 | ) { 47 | /** 48 | * Returns the element in the given [slot]. 49 | * 50 | * @return the element 51 | */ 52 | public operator fun get(slot: Int): ItemStackElement = internalPane.getAdjusted(slot, slotType) 53 | 54 | /** 55 | * Returns the element at the given [x], [y]-coordinates. 56 | * 57 | * @return the element 58 | */ 59 | public operator fun get( 60 | x: Int, 61 | y: Int, 62 | ): ItemStackElement = get(PaperUtils.gridToSlot(Vector2.at(x, y))) 63 | 64 | /** 65 | * Sets the [element] in the given [slot]. 66 | * 67 | * @param slot the slot 68 | * @param element the element 69 | */ 70 | public operator fun set( 71 | slot: Int, 72 | element: ItemStackElement, 73 | ) { 74 | mutate { internalPane.setAdjusted(slot, slotType, element) } 75 | } 76 | 77 | /** 78 | * Sets the element at the given [x], [y]-coordinates. 79 | * 80 | * @param x the x coordinate 81 | * @param y the y coordinate 82 | * @param element the element 83 | */ 84 | public operator fun set( 85 | x: Int, 86 | y: Int, 87 | element: ItemStackElement, 88 | ) { 89 | set(PaperUtils.gridToSlot(Vector2.at(x, y)), element) 90 | } 91 | 92 | /** 93 | * Sets an element at the given position. 94 | * 95 | * @param position the vector coordinate 96 | * @param element the element 97 | */ 98 | public operator fun set( 99 | position: Vector2, 100 | element: ItemStackElement, 101 | ): Unit = 102 | mutate { 103 | element(element, position.x, position.y) 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /kotlin/src/main/kotlin/org/incendo/interfaces/kotlin/paper/PaperExt.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.kotlin.paper 2 | 3 | import net.kyori.adventure.text.Component 4 | import org.bukkit.entity.Player 5 | import org.bukkit.event.inventory.InventoryClickEvent 6 | import org.bukkit.inventory.ItemStack 7 | import org.incendo.interfaces.core.Interface 8 | import org.incendo.interfaces.core.arguments.InterfaceArguments 9 | import org.incendo.interfaces.core.click.ClickContext 10 | import org.incendo.interfaces.core.click.ClickHandler 11 | import org.incendo.interfaces.core.pane.Pane 12 | import org.incendo.interfaces.core.view.InterfaceView 13 | import org.incendo.interfaces.paper.PlayerViewer 14 | import org.incendo.interfaces.paper.element.ItemStackElement 15 | import org.incendo.interfaces.paper.pane.ChestPane 16 | import org.incendo.interfaces.paper.pane.CombinedPane 17 | import org.incendo.interfaces.paper.type.ChestInterface 18 | import org.incendo.interfaces.paper.type.CombinedInterface 19 | import org.incendo.interfaces.paper.type.PlayerInterface 20 | import org.incendo.interfaces.paper.type.TitledInterface 21 | 22 | /** 23 | * Builds a new [ChestInterface] using the given [builder]. 24 | * 25 | * @return built interface 26 | */ 27 | public fun buildChestInterface(builder: MutableChestInterfaceBuilder.() -> Unit): ChestInterface = 28 | MutableChestInterfaceBuilder().also(builder).toBuilder().build() 29 | 30 | /** 31 | * Builds a new [PlayerInterface] using the given builder. 32 | * 33 | * @return build interface 34 | */ 35 | public fun buildPlayerInterface(builder: MutablePlayerInterfaceBuilder.() -> Unit): PlayerInterface = 36 | MutablePlayerInterfaceBuilder().also(builder).toBuilder().build() 37 | 38 | /** 39 | * Builds a new [CombinedInterface] using the given builder. 40 | * 41 | * @return build interface 42 | */ 43 | public fun buildCombinedInterface(builder: MutableCombinedInterfaceBuilder.() -> Unit): CombinedInterface = 44 | MutableCombinedInterfaceBuilder().also(builder).toBuilder().build() 45 | 46 | // 47 | 48 | /** 49 | * Opens the [interface] for `this` player. 50 | * 51 | * @param `interface` interface to open 52 | * @param arguments the arguments to pass to the interface 53 | * @return the interface view 54 | */ 55 | public fun Player.open( 56 | `interface`: Interface, 57 | arguments: InterfaceArguments? = null, 58 | ): InterfaceView = 59 | if (arguments == null) { 60 | `interface`.open(this.asViewer()) 61 | } else { 62 | `interface`.open(this.asViewer(), arguments) 63 | } 64 | 65 | /** 66 | * Opens the [interface] for `this` player. 67 | * 68 | * @param `interface` interface to open 69 | * @param arguments the arguments to pass to the interface 70 | * @param title the title 71 | * @return the interface view 72 | */ 73 | public fun > Player.open( 74 | `interface`: I, 75 | arguments: InterfaceArguments? = null, 76 | title: Component = `interface`.title(), 77 | ): InterfaceView = 78 | if (arguments == null) { 79 | `interface`.open(this.asViewer(), title) 80 | } else { 81 | `interface`.open(this.asViewer(), arguments, title) 82 | } 83 | 84 | /** 85 | * Returns a [PlayerViewer] instance wrapping `this` player. 86 | * 87 | * @return viewer instance 88 | */ 89 | public fun Player.asViewer(): PlayerViewer = PlayerViewer.of(this) 90 | // 91 | 92 | /** 93 | * Returns an [ItemStackElement] instance wrapping `this` item stack. 94 | * 95 | * @param handler optional click handler 96 | * @return element instance 97 | */ 98 | public fun ItemStack.asElement(handler: GenericClickHandler? = null): ItemStackElement = 99 | if (handler == null) ItemStackElement.of(this) else ItemStackElement.of(this, handler) 100 | 101 | public typealias GenericClickHandler

= 102 | ClickHandler> 103 | 104 | public typealias ChestClickHandler = GenericClickHandler 105 | 106 | public typealias CombinedClickHandler = GenericClickHandler 107 | -------------------------------------------------------------------------------- /kotlin/src/main/kotlin/org/incendo/interfaces/kotlin/paper/MutablePlayerInterfaceBuilder.kt: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.kotlin.paper 2 | 3 | import org.bukkit.event.inventory.InventoryClickEvent 4 | import org.incendo.interfaces.core.click.ClickHandler 5 | import org.incendo.interfaces.core.transform.InterfaceProperty 6 | import org.incendo.interfaces.core.transform.ReactiveTransform 7 | import org.incendo.interfaces.core.transform.Transform 8 | import org.incendo.interfaces.core.view.InterfaceView 9 | import org.incendo.interfaces.kotlin.MutableInterfaceBuilder 10 | import org.incendo.interfaces.paper.PlayerViewer 11 | import org.incendo.interfaces.paper.click.InventoryClickContext 12 | import org.incendo.interfaces.paper.pane.PlayerPane 13 | import org.incendo.interfaces.paper.type.PlayerInterface 14 | import org.incendo.interfaces.paper.view.PlayerInventoryView 15 | import org.incendo.interfaces.paper.view.PlayerView 16 | 17 | @Suppress("unused") 18 | public class MutablePlayerInterfaceBuilder : 19 | MutableInterfaceBuilder< 20 | PlayerPane, 21 | InventoryClickEvent, 22 | PlayerViewer, 23 | InventoryClickContext, 24 | > { 25 | private var internalBuilder: PlayerInterface.Builder = PlayerInterface.builder() 26 | 27 | // 28 | 29 | /** The click handler of the interface. */ 30 | public var clickHandler: 31 | ClickHandler< 32 | PlayerPane, 33 | InventoryClickEvent, 34 | PlayerViewer, 35 | InventoryClickContext, 36 | > 37 | get() = internalBuilder.clickHandler() 38 | set(value) = mutate { internalBuilder.clickHandler(value) } 39 | // 40 | 41 | // 42 | 43 | /** 44 | * Sets whether the interface should update. 45 | * 46 | * @param toggle true if the interface should update, false if not 47 | * @param interval how many ticks to wait between updates 48 | */ 49 | public fun updates( 50 | toggle: Boolean = true, 51 | interval: Int = 1, 52 | ): Unit = 53 | mutate { 54 | internalBuilder.updates(toggle, interval) 55 | } 56 | 57 | /** 58 | * Adds the given [transform] to the interface. 59 | * 60 | * @param transform transform to add 61 | * @param priority the priority 62 | */ 63 | public fun addTransform( 64 | priority: Int = 1, 65 | transform: Transform, 66 | ): Unit = 67 | mutate { 68 | if (transform is ReactiveTransform) { 69 | internalBuilder.addReactiveTransform(priority, transform) as PlayerInterface.Builder 70 | } else { 71 | internalBuilder.addTransform(priority, transform) 72 | } 73 | } 74 | 75 | /** 76 | * Adds the given [transform] to the interface. 77 | * 78 | * @param transform transform to add 79 | */ 80 | @Suppress("unchecked_cast") 81 | public fun addTransform( 82 | priority: Int = 1, 83 | vararg properties: InterfaceProperty<*>, 84 | transform: (PlayerPane, PlayerView) -> PlayerPane, 85 | ): Unit = 86 | mutate { 87 | internalBuilder.addTransform( 88 | priority, 89 | transform as (PlayerPane, InterfaceView) -> PlayerPane, 90 | *properties, 91 | ) 92 | } 93 | 94 | /** 95 | * Adds the given [transform] to the interface. 96 | * 97 | * @param transform transform to add 98 | */ 99 | public fun withTransform( 100 | vararg properties: InterfaceProperty<*>, 101 | transform: (MutablePlayerPaneView) -> Unit, 102 | ) { 103 | addTransform(1, *properties) { PlayerPane, interfaceView -> 104 | MutablePlayerPaneView(PlayerPane, interfaceView).also(transform).toPlayerPane() 105 | } 106 | } 107 | // 108 | 109 | private fun mutate(mutator: PlayerInterface.Builder.() -> PlayerInterface.Builder) { 110 | this.internalBuilder = internalBuilder.mutator() 111 | } 112 | 113 | /** 114 | * Converts this mutable player interface builder to a [PlayerInterface.Builder]. 115 | * 116 | * @return the converted builder 117 | */ 118 | public fun toBuilder(): PlayerInterface.Builder = internalBuilder 119 | } 120 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/element/ItemStackElement.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.element; 2 | 3 | import org.bukkit.Material; 4 | import org.bukkit.event.inventory.InventoryClickEvent; 5 | import org.bukkit.inventory.ItemStack; 6 | import org.checkerframework.checker.nullness.qual.NonNull; 7 | import org.incendo.interfaces.core.click.ClickContext; 8 | import org.incendo.interfaces.core.click.ClickHandler; 9 | import org.incendo.interfaces.core.element.Element; 10 | import org.incendo.interfaces.core.pane.Pane; 11 | import org.incendo.interfaces.paper.PlayerViewer; 12 | import org.incendo.interfaces.paper.pane.ChestPane; 13 | import org.incendo.interfaces.paper.type.Clickable; 14 | 15 | import java.util.Objects; 16 | 17 | /** 18 | * Holds an {@link ItemStack} in an element. 19 | * 20 | * @param the pane type 21 | * @see ChestPane 22 | */ 23 | public class ItemStackElement implements Element, Clickable { 24 | 25 | private final @NonNull ItemStack itemStack; 26 | private final @NonNull ClickHandler> handler; 28 | 29 | /** 30 | * Constructs {@code ItemStackElement}. 31 | * 32 | * @param itemStack the {@link ItemStack} 33 | */ 34 | public ItemStackElement(final @NonNull ItemStack itemStack) { 35 | this.itemStack = itemStack; 36 | this.handler = ClickHandler.dummy(); 37 | } 38 | 39 | /** 40 | * Constructs {@code ItemStackElement}. 41 | * 42 | * @param itemStack the {@link ItemStack} 43 | * @param clickHandler the click handler 44 | */ 45 | public ItemStackElement( 46 | final @NonNull ItemStack itemStack, 47 | final @NonNull ClickHandler> clickHandler 49 | ) { 50 | this.itemStack = itemStack; 51 | this.handler = clickHandler; 52 | } 53 | 54 | /** 55 | * Returns an empty {@code ItemStackElement}. 56 | * 57 | * @param the pane type 58 | * @return an empty {@code ItemStackElement}. 59 | */ 60 | public static @NonNull ItemStackElement empty() { 61 | return new ItemStackElement<>(new ItemStack(Material.AIR)); 62 | } 63 | 64 | /** 65 | * Returns an {@code ItemStackElement} with the provided ItemStack. 66 | * 67 | * @param itemStack the ItemStack 68 | * @param the pane type 69 | * @return the element 70 | */ 71 | public static @NonNull ItemStackElement of(final @NonNull ItemStack itemStack) { 72 | return new ItemStackElement<>(itemStack); 73 | } 74 | 75 | /** 76 | * Returns an {@code ItemStackElement} with the provided ItemStack. 77 | * 78 | * @param itemStack the ItemStack 79 | * @param handler the handler 80 | * @param the pane type 81 | * @return the element 82 | */ 83 | public static @NonNull ItemStackElement of( 84 | final @NonNull ItemStack itemStack, 85 | final @NonNull ClickHandler> handler 87 | ) { 88 | return new ItemStackElement<>(itemStack, handler); 89 | } 90 | 91 | /** 92 | * Returns this element's {@link ItemStack}. 93 | * 94 | * @return the {@link ItemStack} 95 | */ 96 | public @NonNull ItemStack itemStack() { 97 | return this.itemStack; 98 | } 99 | 100 | /** 101 | * Returns the click handler. 102 | * 103 | * @return the click handler 104 | */ 105 | public @NonNull ClickHandler> clickHandler() { 107 | return this.handler; 108 | } 109 | 110 | @Override 111 | public final boolean equals(final Object o) { 112 | if (this == o) { 113 | return true; 114 | } 115 | if (o == null || getClass() != o.getClass()) { 116 | return false; 117 | } 118 | ItemStackElement that = (ItemStackElement) o; 119 | return this.itemStack.equals(that.itemStack) && this.handler.equals(that.handler); 120 | } 121 | 122 | @Override 123 | public final int hashCode() { 124 | return Objects.hash(this.itemStack); 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/pane/CombinedPane.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.pane; 2 | 3 | import org.checkerframework.checker.nullness.qual.NonNull; 4 | import org.incendo.interfaces.core.element.Element; 5 | import org.incendo.interfaces.core.pane.GridPane; 6 | import org.incendo.interfaces.core.util.Vector2; 7 | import org.incendo.interfaces.paper.element.ItemStackElement; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.Collection; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | public final class CombinedPane implements GridPane> { 16 | 17 | public static final int PLAYER_INVENTORY_ROWS = 4; 18 | 19 | private final @NonNull Map<@NonNull Vector2, @NonNull ItemStackElement> elements; 20 | 21 | private final ItemStackElement[] hotbar; 22 | 23 | private final int rows; 24 | 25 | /** 26 | * Constructs {@code CombinePane}. 27 | * 28 | * @param rows the amount of rows 29 | */ 30 | public CombinedPane(final int rows) { 31 | this.rows = rows; 32 | this.elements = new HashMap<>(); 33 | this.hotbar = new ItemStackElement[9]; 34 | 35 | for (int x = 0; x < ChestPane.MINECRAFT_CHEST_WIDTH; x++) { 36 | for (int y = 0; y < this.rows; y++) { 37 | this.elements.put(Vector2.at(x, y), ItemStackElement.empty()); 38 | } 39 | } 40 | 41 | for (int x = 0; x < ChestPane.MINECRAFT_CHEST_WIDTH; x++) { 42 | this.hotbar[x] = ItemStackElement.empty(); 43 | } 44 | } 45 | 46 | /** 47 | * Constructs {@code CombinePane}. 48 | * 49 | * @param rows amount of rows 50 | * @param elements the chest elements 51 | * @param hotbar the hotbar elements 52 | */ 53 | public CombinedPane( 54 | final int rows, 55 | final @NonNull Map<@NonNull Vector2, @NonNull ItemStackElement> elements, 56 | final ItemStackElement[] hotbar 57 | ) { 58 | this.rows = rows; 59 | this.elements = elements; 60 | this.hotbar = hotbar; 61 | } 62 | 63 | /** 64 | * Returns the inventory elements as a vector map. 65 | * 66 | * @return the elements 67 | */ 68 | public @NonNull Map<@NonNull Vector2, @NonNull ItemStackElement> inventoryElements() { 69 | return this.elements; 70 | } 71 | 72 | /** 73 | * Returns the hotbar elements as a 2d array. 74 | * 75 | * @return the elements 76 | */ 77 | public @NonNull ItemStackElement[] hotbarElements() { 78 | return this.hotbar; 79 | } 80 | 81 | /** 82 | * Returns the amount of rows this pane has. 83 | * 84 | * @return the amount of rows 85 | */ 86 | public int rows() { 87 | return this.rows; 88 | } 89 | 90 | @Override 91 | public @NonNull Collection elements() { 92 | return new ArrayList<>(this.elements.values()); 93 | } 94 | 95 | @Override 96 | public @NonNull CombinedPane element( 97 | final @NonNull ItemStackElement element, 98 | final int x, 99 | final int y 100 | ) { 101 | final Map> newElements = new HashMap<>(this.elements); 102 | newElements.put(Vector2.at(x, y), element); 103 | 104 | return new CombinedPane(this.rows, newElements, this.hotbar); 105 | } 106 | 107 | /** 108 | * Create a new combined pane with a new element in the hotbar 109 | * 110 | * @param element the element to use 111 | * @param x the slot in the hotbar 112 | * @return a new combined pane 113 | */ 114 | public @NonNull CombinedPane hotbar( 115 | final @NonNull ItemStackElement element, 116 | final int x 117 | ) { 118 | final ItemStackElement[] newHotbar = Arrays.copyOf(this.hotbar, this.hotbar.length); 119 | newHotbar[x] = element; 120 | 121 | return new CombinedPane(this.rows, this.elements, newHotbar); 122 | } 123 | 124 | @Override 125 | public @NonNull ItemStackElement element(final int x, final int y) { 126 | return this.elements.get(Vector2.at(x, y)); 127 | } 128 | 129 | /** 130 | * Retrieve an element from the hotbar 131 | * 132 | * @param x the slot in the hotbar 133 | * @return the element 134 | */ 135 | public @NonNull ItemStackElement hotbar( 136 | final int x 137 | ) { 138 | return this.hotbar[x]; 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /paper/src/main/java/org/incendo/interfaces/paper/click/InventoryClickContext.java: -------------------------------------------------------------------------------- 1 | package org.incendo.interfaces.paper.click; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.bukkit.event.inventory.ClickType; 5 | import org.bukkit.event.inventory.InventoryClickEvent; 6 | import org.bukkit.event.inventory.InventoryType; 7 | import org.bukkit.inventory.Inventory; 8 | import org.bukkit.inventory.InventoryHolder; 9 | import org.checkerframework.checker.nullness.qual.NonNull; 10 | import org.incendo.interfaces.core.click.Click; 11 | import org.incendo.interfaces.core.click.ClickContext; 12 | import org.incendo.interfaces.core.click.clicks.Clicks; 13 | import org.incendo.interfaces.core.pane.Pane; 14 | import org.incendo.interfaces.core.view.InterfaceView; 15 | import org.incendo.interfaces.paper.PlayerViewer; 16 | import org.incendo.interfaces.paper.view.ChestView; 17 | import org.incendo.interfaces.paper.view.CombinedView; 18 | import org.incendo.interfaces.paper.view.PlayerInventoryView; 19 | 20 | import java.util.Objects; 21 | 22 | /** 23 | * The click context of a chest. 24 | * 25 | * @param the pane type 26 | * @param the viewer type 27 | */ 28 | @SuppressWarnings("unused") 29 | public final class InventoryClickContext> implements 30 | ClickContext { 31 | 32 | private final @NonNull InventoryClickEvent event; 33 | private final @NonNull U view; 34 | private final @NonNull Click click; 35 | 36 | /** 37 | * Constructs {@code InventoryClickContext}. 38 | * 39 | * @param event the event 40 | * @param player whether the player clicked their own inventory 41 | * @param interact whether the click was triggered by an interact event 42 | */ 43 | @SuppressWarnings("unchecked") 44 | public InventoryClickContext( 45 | final @NonNull InventoryClickEvent event, 46 | final boolean player, 47 | final boolean interact 48 | ) { 49 | this.event = event; 50 | 51 | final @NonNull Inventory inventory = event.getInventory(); 52 | 53 | if (player) { 54 | this.view = (U) Objects.requireNonNull(PlayerInventoryView.forPlayer((Player) event.getWhoClicked())); 55 | } else if (inventory.getType() == InventoryType.CHEST) { 56 | InventoryHolder holder = inventory.getHolder(); 57 | 58 | if (holder instanceof CombinedView) { 59 | this.view = this.viewFromCombinedClick(); 60 | } else if (!(inventory.getHolder() instanceof ChestView)) { 61 | throw new IllegalArgumentException( 62 | "The InventoryHolder wasn't a ChestView." 63 | ); 64 | } else { 65 | if (event.getSlot() != event.getRawSlot()) { 66 | this.view = (U) Objects.requireNonNull(PlayerInventoryView.forPlayer((Player) event.getWhoClicked())); 67 | } else { 68 | this.view = (U) inventory.getHolder(); 69 | } 70 | } 71 | } else { 72 | throw new IllegalArgumentException("Inventory type " + inventory.getType() + " is not a valid inventory."); 73 | } 74 | 75 | if (this.event.isLeftClick()) { 76 | this.click = Clicks.leftClick( 77 | this.event, 78 | this.event.isShiftClick(), 79 | interact 80 | ); 81 | } else if (this.event.isRightClick()) { 82 | this.click = Clicks.rightClick( 83 | this.event, 84 | this.event.isShiftClick(), 85 | interact 86 | ); 87 | } else if (this.event.getClick() == ClickType.MIDDLE) { 88 | this.click = Clicks.rightClick( 89 | this.event, 90 | this.event.isShiftClick(), 91 | interact 92 | ); 93 | } else { 94 | /* ??? */ 95 | this.click = Clicks.unknownClick(this.event); 96 | } 97 | } 98 | 99 | @Override 100 | public @NonNull InventoryClickEvent cause() { 101 | return this.event; 102 | } 103 | 104 | @Override 105 | public @NonNull ClickStatus status() { 106 | if (!this.event.isCancelled()) { 107 | return ClickStatus.ALLOW; 108 | } else { 109 | return ClickStatus.DENY; 110 | } 111 | } 112 | 113 | @Override 114 | public void status( 115 | @NonNull final ClickStatus status 116 | ) { 117 | switch (status) { 118 | case ALLOW: 119 | this.event.setCancelled(false); 120 | break; 121 | case DENY: 122 | this.event.setCancelled(true); 123 | break; 124 | default: 125 | break; 126 | } 127 | } 128 | 129 | @Override 130 | public @NonNull U view() { 131 | return this.view; 132 | } 133 | 134 | @Override 135 | public @NonNull PlayerViewer viewer() { 136 | return PlayerViewer.of((Player) this.event.getWhoClicked()); 137 | } 138 | 139 | @Override 140 | public @NonNull Click click() { 141 | return this.click; 142 | } 143 | 144 | /** 145 | * Returns the slot 146 | * 147 | * @return the slot 148 | */ 149 | public int slot() { 150 | return this.event.getSlot(); 151 | } 152 | 153 | /** 154 | * Returns the raw slot 155 | * 156 | * @return the raw slot 157 | */ 158 | public int rawSlot() { 159 | return this.event.getRawSlot(); 160 | } 161 | 162 | private U viewFromCombinedClick() { 163 | return (U) this.event.getInventory().getHolder(); 164 | } 165 | 166 | } 167 | --------------------------------------------------------------------------------