diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/RootView.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/RootView.java index edb90a49..017da5f5 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/RootView.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/RootView.java @@ -1,6 +1,5 @@ package me.devnatan.inventoryframework; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -10,9 +9,8 @@ import me.devnatan.inventoryframework.pipeline.Pipeline; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.UnmodifiableView; -public interface RootView extends VirtualView, Iterable { +public interface RootView extends VirtualView { /** * The unique identifier of this view. @@ -24,72 +22,14 @@ public interface RootView extends VirtualView, Iterable { /** * All contexts linked to this view. - * - * @return An unmodifiable set of all currently active contexts in this view. - */ - @NotNull - @UnmodifiableView - Set getContexts(); - - /** - * Returns the context that is linked to the specified viewer in this view. - *

- * This is an internal inventory-framework API that should not be used from outside of - * this library. No compatibility guarantees are provided. - * - * @param viewer The viewer. - * @return The context of the viewer in this context. - * @throws IllegalArgumentException If there's no context linked to the given viewer. - */ - @ApiStatus.Internal - @NotNull - IFContext getContext(@NotNull Viewer viewer); - - /** - * Returns the context that is linked to the specified viewer in this view. - *

- * This is an internal inventory-framework API that should not be used from outside of - * this library. No compatibility guarantees are provided. - * - * @param viewerId The id of the viewer. - * @return The context of the viewer in this context. - * @throws IllegalArgumentException If there's no context linked to the given viewer. - */ - @NotNull - IFContext getContext(@NotNull String viewerId); - - /** - * Adds a context to this view. - *

- * This is an internal inventory-framework API that should not be used from outside of - * this library. No compatibility guarantees are provided. - * - * @param context The context to add. - */ - @ApiStatus.Internal - void addContext(@NotNull IFContext context); - - /** - * Removes a given context from this view if that context is linked to this view. *

* This is an internal inventory-framework API that should not be used from outside of * this library. No compatibility guarantees are provided. * - * @param context The context to remove. - */ - @ApiStatus.Internal - void removeContext(@NotNull IFContext context); - - /** - * Renders a given context in this view. - *

- * This is an internal inventory-framework API that should not be used from outside of - * this library. No compatibility guarantees are provided. - * - * @param context The context to render. + * @return An unmodifiable set of all currently active contexts in this view. */ @ApiStatus.Internal - void renderContext(@NotNull IFContext context); + Set getInternalContexts(); /** * Called when the view is about to be configured, the returned object will be the view's @@ -127,23 +67,6 @@ public interface RootView extends VirtualView, Iterable { @NotNull Pipeline getPipeline(); - /** - * Opens this view to more than one viewer. - *

- * This is an internal inventory-framework API that should not be used from outside of - * this library. No compatibility guarantees are provided. - * - * @param viewers The viewers that'll see this view. - * @param initialData The initial data. - */ - @ApiStatus.Internal - void open(@NotNull List viewers, Object initialData); - - /** - * Closes all contexts that are currently active in this view. - */ - void closeForEveryone(); - /** * The ElementFactory for this view. *

@@ -158,25 +81,26 @@ public interface RootView extends VirtualView, Iterable { /** * Runs a task in the next tick. + *

+ * This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. * * @param task The task to run. */ + @ApiStatus.Internal void nextTick(Runnable task); /** - * The IFViewFrame for this view. - *

* This is an internal inventory-framework API that should not be used from outside of * this library. No compatibility guarantees are provided. - * - * @return The current framework that holds this view. - * @throws UnsupportedOperationException If this view doesn't support a framework. */ @ApiStatus.Internal - IFViewFrame getFramework(); - Job getScheduledUpdateJob(); + /** + * This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. + */ @ApiStatus.Internal void setScheduledUpdateJob(@NotNull Job job); diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/UnsupportedOperationInSharedContextException.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/UnsupportedOperationInSharedContextException.java new file mode 100644 index 00000000..ad752386 --- /dev/null +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/UnsupportedOperationInSharedContextException.java @@ -0,0 +1,17 @@ +package me.devnatan.inventoryframework; + +/** + * Thrown when an operation is called in a shared context, but it's not supported for some reason. + * + * @see Shared Contexts on Wiki + */ +public final class UnsupportedOperationInSharedContextException extends InventoryFrameworkException { + + public UnsupportedOperationInSharedContextException() { + super("This operation is not supported in shared contexts."); + } + + public UnsupportedOperationInSharedContextException(String replacement) { + super(String.format("This operation is not supported in shared contexts. Use #%s instead.", replacement)); + } +} diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/ViewContainer.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/ViewContainer.java index d9ab395b..7abe3024 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/ViewContainer.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/ViewContainer.java @@ -1,9 +1,7 @@ package me.devnatan.inventoryframework; -import java.util.List; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.Unmodifiable; public interface ViewContainer { @@ -77,18 +75,12 @@ public interface ViewContainer { */ int getColumnsCount(); - @NotNull - @Unmodifiable - List getViewers(); - void open(@NotNull Viewer viewer); void close(); void close(@NotNull Viewer viewer); - void changeTitle(@Nullable String title); - void changeTitle(@Nullable String title, @NotNull Viewer target); boolean isEntityContainer(); diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/ViewType.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/ViewType.java index c583ed32..37b2c240 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/ViewType.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/ViewType.java @@ -20,6 +20,7 @@ public final class ViewType { public static final ViewType SHULKER_BOX = new ViewType("shulker-box", 27, 3, 9); public static final ViewType SMOKER = new ViewType("smoker", 3, 2, 2, false, new int[] {2}); public static final ViewType VILLAGER_TRADING = new ViewType("villager-trading", 3, 1, 3, false, new int[] {2}); + public static final ViewType PLAYER = new ViewType("player", 54, 3, 9, false); private static final int[] EMPTY_RESULT_SLOTS = new int[0]; diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/Viewer.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/Viewer.java index 087dbbdd..cab80117 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/Viewer.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/Viewer.java @@ -1,7 +1,11 @@ package me.devnatan.inventoryframework; +import me.devnatan.inventoryframework.context.IFRenderContext; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; +@ApiStatus.Internal public interface Viewer { /** @@ -31,4 +35,12 @@ public interface Viewer { */ @NotNull ViewContainer getSelfContainer(); + + @NotNull + IFRenderContext getContext(); + + void setContext(IFRenderContext context); + + @Contract("_ -> this") + Viewer withContext(IFRenderContext context); } diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/ItemComponentBuilder.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/ItemComponentBuilder.java index 70bbe815..c0d64cf2 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/ItemComponentBuilder.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/ItemComponentBuilder.java @@ -3,8 +3,8 @@ import me.devnatan.inventoryframework.context.IFContext; import org.jetbrains.annotations.ApiStatus; -public interface ItemComponentBuilder, C extends IFContext> - extends ComponentBuilder { +public interface ItemComponentBuilder, Context extends IFContext> + extends ComponentBuilder { /** * Sets the slot that the item will be positioned. @@ -12,11 +12,11 @@ public interface ItemComponentBuilder, C ex * @param slot The item slot. * @return This item builder. */ - S withSlot(int slot); + Self withSlot(int slot); // TODO needs documentation @ApiStatus.Experimental - S withSlot(int row, int column); + Self withSlot(int row, int column); @ApiStatus.Internal boolean isContainedWithin(int position); diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/PaginationElementFactory.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/PaginationElementFactory.java index a07719b2..317b7a2b 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/PaginationElementFactory.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/component/PaginationElementFactory.java @@ -1,7 +1,7 @@ package me.devnatan.inventoryframework.component; @FunctionalInterface -public interface PaginationElementFactory { +public interface PaginationElementFactory { - ComponentFactory create(T context, int index, int slot, V value); + ComponentFactory create(Context context, int index, int slot, V value); } diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFCloseContext.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFCloseContext.java index 6ea74f30..1c664b11 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFCloseContext.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFCloseContext.java @@ -1,8 +1,15 @@ package me.devnatan.inventoryframework.context; +import org.jetbrains.annotations.ApiStatus; + public interface IFCloseContext extends IFConfinedContext { - IFContext getParent(); + /** + * This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. + */ + @ApiStatus.Internal + IFRenderContext getParent(); /** * Cancellation state of that context. diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFConfinedContext.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFConfinedContext.java index b35c32fe..6a638df3 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFConfinedContext.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFConfinedContext.java @@ -17,7 +17,6 @@ public interface IFConfinedContext extends IFContext { * * @return The {@link Viewer} in the current scope of execution. */ - @NotNull Viewer getViewer(); /** diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFContext.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFContext.java index b626e86f..0b137401 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFContext.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFContext.java @@ -12,16 +12,19 @@ import me.devnatan.inventoryframework.state.StateValueHost; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnmodifiableView; public interface IFContext extends VirtualView, StateValueHost { /** * An unique id for this context. + *

+ * This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. * * @return The unique identifier for this context. */ + @ApiStatus.Internal @NotNull UUID getId(); @@ -45,26 +48,37 @@ public interface IFContext extends VirtualView, StateValueHost { List getViewers(); /** - * An unmodifiable view of all viewers that are tied to this context. + * A Map containing all viewers in that context. + *

+ * This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. * - * @return All unmodifiable view of all viewers. + * @return A Map containing all viewers in that context. */ @NotNull - @UnmodifiableView + @ApiStatus.Internal Map getIndexedViewers(); /** * Adds a new viewer to this context. + *

+ * This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. * * @param viewer The viewer that'll be added. */ + @ApiStatus.Internal void addViewer(@NotNull Viewer viewer); /** * Removes a new viewer to this context. + *

+ * This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. * * @param viewer The viewer that'll be removed. */ + @ApiStatus.Internal void removeViewer(@NotNull Viewer viewer); /** @@ -79,7 +93,6 @@ public interface IFContext extends VirtualView, StateValueHost { * * @return The container of this context. */ - @NotNull ViewContainer getContainer(); /** @@ -90,17 +103,6 @@ public interface IFContext extends VirtualView, StateValueHost { @NotNull RootView getRoot(); - /** - * The actual title of this context. - *

- * If the title has been dynamically changed, it will return the {@link #getUpdatedTitle() updated title}. - * - * @return The updated title, the current title of this view, if null will return - * the default title for this view type. - */ - @NotNull - String getTitle(); - /** * The initial title of this context, that is, even if it has been changed, it will return the * title that has been initially defined. @@ -110,59 +112,6 @@ public interface IFContext extends VirtualView, StateValueHost { @NotNull String getInitialTitle(); - /** - * Title that has been {@link #updateTitleForEveryone(String) dynamically changed} in this context. - * - * @return The updated title or null if it wasn't updated. - * @see #updateTitleForEveryone(String) - */ - @Nullable - String getUpdatedTitle(); - - /** - * Updates the container title for everyone that's viewing it. - * - *

This should not be used before the container is opened, if you need to set the __initial - * title__ use {@link IFOpenContext#modifyConfig()} on open handler instead. - * - *

This method is version dependant, so it may be that your server version is not yet - * supported, if you try to use this method and fail (can fail silently), report it to the - * library developers to add support to your version. - * - * @param title The new container title. - */ - void updateTitleForEveryone(@NotNull String title); - - /** - * Updates the container title to all viewers in this context, to the initially defined title. - * Must be used after {@link #updateTitleForEveryone(String)} to take effect. - */ - void resetTitleForEveryone(); - - /** - * Closes this context's container to all viewers who are viewing it. - */ - void closeForEveryone(); - - /** - * Opens a new view for all viewers in that context. - *

- * This context will be immediately invalidated if there are no viewers left after opening. - * - * @param other The view to be opened. - */ - void openForEveryone(Class other); - - /** - * Opens a new view for all viewers in that context with an initially defined data. - *

- * This context will be immediately invalidated if there are no viewers left after opening. - * - * @param other The view to be opened. - * @param initialData The initial data. - */ - void openForEveryone(Class other, Object initialData); - /** * All components in this context. * diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFOpenContext.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFOpenContext.java index d457c099..063151ec 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFOpenContext.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFOpenContext.java @@ -1,7 +1,9 @@ package me.devnatan.inventoryframework.context; import java.util.concurrent.CompletableFuture; +import me.devnatan.inventoryframework.ViewConfig; import me.devnatan.inventoryframework.ViewConfigBuilder; +import me.devnatan.inventoryframework.Viewer; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -9,11 +11,18 @@ * This context is created before the container is opened, it is used for cancellation by previously * defined data also can be used to change the title and size of the container before the rendering intent. */ -public interface IFOpenContext extends IFConfinedContext { +public interface IFOpenContext extends IFContext { + + /** + * This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. + */ + @ApiStatus.Internal + Viewer getSubject(); /** * The task that will run before this context transitions from opening to rendering context. - * + *

* This is an internal inventory-framework API that should not be used from outside of * this library. No compatibility guarantees are provided. * @@ -56,4 +65,11 @@ public interface IFOpenContext extends IFConfinedContext { */ @NotNull ViewConfigBuilder modifyConfig(); + + /** + * This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. + */ + @ApiStatus.Internal + ViewConfig getModifiedConfig(); } diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFSlotClickContext.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFSlotClickContext.java index 5f3c84a9..52896c7f 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFSlotClickContext.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFSlotClickContext.java @@ -1,5 +1,7 @@ package me.devnatan.inventoryframework.context; +import me.devnatan.inventoryframework.ViewContainer; +import me.devnatan.inventoryframework.component.Component; import org.jetbrains.annotations.NotNull; /** @@ -8,7 +10,13 @@ * * @see IFSlotContext */ -public interface IFSlotClickContext extends IFSlotContext { +public interface IFSlotClickContext extends IFSlotContext, IFConfinedContext { + + // TODO needs documentation + @NotNull + ViewContainer getClickedContainer(); + + Component getComponent(); int getClickedSlot(); diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFSlotContext.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFSlotContext.java index 94867ec1..2335ffad 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFSlotContext.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFSlotContext.java @@ -1,8 +1,6 @@ package me.devnatan.inventoryframework.context; -import me.devnatan.inventoryframework.component.Component; import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Nullable; /** * Represents a context in which there is a specific slot related to it, the main context @@ -25,7 +23,7 @@ public interface IFSlotContext extends IFContext { * @return The parent context of this context. */ @ApiStatus.Internal - IFContext getParent(); + IFRenderContext getParent(); /** * Clears this slot from the current context. @@ -88,40 +86,4 @@ public interface IFSlotContext extends IFContext { * @return If this context originated from the actor's container */ boolean isOnEntityContainer(); - - /** - * Checks if the item in this context has been changed. - * - *

This is an internal inventory-framework API that should not be used from outside of - * this library. No compatibility guarantees are provided. - * - * @return If the item in this context has been changed. - */ - @ApiStatus.Internal - boolean hasChanged(); - - /** - * Marks this context as changed. - * - *

Improperly changing this property can cause unexpected side effects. - * - *

This is an internal inventory-framework API that should not be used from outside of - * this library. No compatibility guarantees are provided. - * - * @param changed If the context item was changed. - */ - @ApiStatus.Internal - void setChanged(boolean changed); - - /** - *

This API is experimental and is not subject to the general compatibility guarantees - * such API may be changed or may be removed completely in any further release. - */ - @ApiStatus.Experimental - boolean isRegistered(); - - // TODO documentation - // can be null if context origin is outside root view container - @Nullable - Component getComponent(); } diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFSlotRenderContext.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFSlotRenderContext.java index 9e0bde13..5f5ed650 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFSlotRenderContext.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFSlotRenderContext.java @@ -2,7 +2,7 @@ import org.jetbrains.annotations.ApiStatus; -public interface IFSlotRenderContext extends IFSlotContext { +public interface IFSlotRenderContext extends IFSlotContext, IFConfinedContext { @ApiStatus.Internal Object getResult(); @@ -10,4 +10,28 @@ public interface IFSlotRenderContext extends IFSlotContext { boolean isCancelled(); void setCancelled(boolean cancelled); + + /** + * Checks if the item in this context has been changed. + * + *

This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. + * + * @return If the item in this context has been changed. + */ + @ApiStatus.Internal + boolean hasChanged(); + + /** + * Marks this context as changed. + * + *

Improperly changing this property can cause unexpected side effects. + * + *

This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. + * + * @param changed If the context item was changed. + */ + @ApiStatus.Internal + void setChanged(boolean changed); } diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFUpdateContext.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFUpdateContext.java new file mode 100644 index 00000000..c3027bec --- /dev/null +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/context/IFUpdateContext.java @@ -0,0 +1,3 @@ +package me.devnatan.inventoryframework.context; + +public interface IFUpdateContext extends IFContext {} diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/internal/ElementFactory.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/internal/ElementFactory.java index 1263198e..fc489695 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/internal/ElementFactory.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/internal/ElementFactory.java @@ -1,14 +1,21 @@ package me.devnatan.inventoryframework.internal; +import java.util.List; import java.util.Map; +import java.util.UUID; import me.devnatan.inventoryframework.RootView; +import me.devnatan.inventoryframework.ViewConfig; import me.devnatan.inventoryframework.ViewContainer; import me.devnatan.inventoryframework.Viewer; import me.devnatan.inventoryframework.VirtualView; import me.devnatan.inventoryframework.component.Component; import me.devnatan.inventoryframework.component.ComponentBuilder; +import me.devnatan.inventoryframework.context.IFCloseContext; import me.devnatan.inventoryframework.context.IFContext; -import me.devnatan.inventoryframework.context.IFSlotContext; +import me.devnatan.inventoryframework.context.IFOpenContext; +import me.devnatan.inventoryframework.context.IFRenderContext; +import me.devnatan.inventoryframework.context.IFSlotClickContext; +import me.devnatan.inventoryframework.context.IFSlotRenderContext; import me.devnatan.inventoryframework.logging.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -38,30 +45,37 @@ public abstract class ElementFactory { public abstract ViewContainer createContainer(@NotNull IFContext context); @NotNull - public abstract Viewer createViewer(Object... parameters); + public abstract Viewer createViewer(@NotNull Object entity, IFRenderContext context); - @NotNull - public abstract String convertViewer(Object input); + public abstract IFOpenContext createOpenContext( + @NotNull RootView root, @Nullable Viewer subject, @NotNull List viewers, Object initialData); - @NotNull - public abstract T createContext( + public abstract IFRenderContext createRenderContext( + @NotNull UUID id, @NotNull RootView root, - ViewContainer container, - Viewer subject, + @NotNull ViewConfig config, + @NotNull ViewContainer container, @NotNull Map viewers, - @NotNull Class kind, - @Nullable IFContext parent, + Viewer subject, Object initialData); - @NotNull - public abstract T createSlotContext( - int slot, - Component component, - @NotNull ViewContainer container, - Viewer subject, - @NotNull Map viewers, - @NotNull IFContext parent, - @NotNull Class kind); + public abstract IFSlotClickContext createSlotClickContext( + int slotClicked, + @NotNull Viewer whoClicked, + @NotNull ViewContainer interactionContainer, + @Nullable Component componentClicked, + @NotNull Object origin); + + public abstract IFSlotRenderContext createSlotRenderContext( + int slot, @NotNull IFRenderContext parent, @Nullable Viewer viewer); + + /** + * Creates a new close context for the current platform. + * + * @param viewer The viewer that is currently the subject of the event of close. + * @return A new close context instance. + */ + public abstract IFCloseContext createCloseContext(@NotNull Viewer viewer, @NotNull IFRenderContext parent); /** * Creates a new platform builder instance. diff --git a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/DefaultRootView.java b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/DefaultRootView.java index 198c4eab..6a466091 100644 --- a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/DefaultRootView.java +++ b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/DefaultRootView.java @@ -1,6 +1,5 @@ package me.devnatan.inventoryframework; -import static java.lang.String.format; import static java.util.Collections.newSetFromMap; import static java.util.Collections.synchronizedMap; import static me.devnatan.inventoryframework.pipeline.StandardPipelinePhases.CLICK; @@ -13,7 +12,6 @@ import static me.devnatan.inventoryframework.pipeline.StandardPipelinePhases.UPDATE; import java.util.*; -import java.util.function.Consumer; import me.devnatan.inventoryframework.context.IFContext; import me.devnatan.inventoryframework.internal.ElementFactory; import me.devnatan.inventoryframework.internal.Job; @@ -21,8 +19,6 @@ import me.devnatan.inventoryframework.state.StateRegistry; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.TestOnly; -import org.jetbrains.annotations.UnmodifiableView; @ApiStatus.NonExtendable public class DefaultRootView implements RootView { @@ -41,53 +37,15 @@ public class DefaultRootView implements RootView { return id; } - @TestOnly - public final @NotNull Set getInternalContexts() { + @Override + public final Set getInternalContexts() { return contexts; } - @Override - public final @NotNull @UnmodifiableView Set getContexts() { + protected final Set getContexts() { return Collections.unmodifiableSet(getInternalContexts()); } - @Override - public final @NotNull IFContext getContext(@NotNull Viewer viewer) { - for (final IFContext context : getInternalContexts()) { - if (context.getIndexedViewers().containsKey(viewer.getId())) return context; - } - - throw new IllegalArgumentException(format("Unable to get context for %s", viewer)); - } - - @Override - public final @NotNull IFContext getContext(@NotNull String viewerId) { - for (final IFContext context : getInternalContexts()) { - if (context.getIndexedViewers().containsKey(viewerId)) return context; - } - - throw new IllegalArgumentException(format("Unable to get context for %s", viewerId)); - } - - @Override - public final void addContext(@NotNull IFContext context) { - synchronized (getInternalContexts()) { - getInternalContexts().add(context); - } - } - - @Override - public final void removeContext(@NotNull IFContext context) { - synchronized (getInternalContexts()) { - getInternalContexts().removeIf(other -> other.getId() == context.getId()); - } - } - - @Override - public final void renderContext(@NotNull IFContext context) { - getPipeline().execute(context); - } - @Override public final @NotNull ViewConfig getConfig() { return config; @@ -105,35 +63,9 @@ public final void setConfig(@NotNull ViewConfig config) { return pipeline; } - @Override - public void open(@NotNull List viewers, Object initialData) { - throw new UnsupportedOperationException("Missing #open(...) implementation"); - } - - @Override - public final void closeForEveryone() { - getContexts().forEach(IFContext::closeForEveryone); - } - @Override public void onInit(ViewConfigBuilder config) {} - @NotNull - @Override - public final Iterator iterator() { - return getContexts().iterator(); - } - - @Override - public final void forEach(Consumer action) { - RootView.super.forEach(action); - } - - @Override - public final Spliterator spliterator() { - return RootView.super.spliterator(); - } - @ApiStatus.Internal public @NotNull ElementFactory getElementFactory() { throw new UnsupportedOperationException("Element factory not provided"); @@ -145,20 +77,15 @@ public void nextTick(Runnable task) { } @Override - public Job getScheduledUpdateJob() { + public final Job getScheduledUpdateJob() { return scheduledUpdateJob; } @Override - public void setScheduledUpdateJob(@NotNull Job job) { + public final void setScheduledUpdateJob(@NotNull Job job) { this.scheduledUpdateJob = job; } - @Override - public IFViewFrame getFramework() { - throw new UnsupportedOperationException("Missing #getFramework() implementation"); - } - @NotNull @Override public final Map getMetadata() { diff --git a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/context/AbstractIFContext.java b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/context/AbstractIFContext.java new file mode 100644 index 00000000..32d9215a --- /dev/null +++ b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/context/AbstractIFContext.java @@ -0,0 +1,172 @@ +package me.devnatan.inventoryframework.context; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import me.devnatan.inventoryframework.InventoryFrameworkException; +import me.devnatan.inventoryframework.UnsupportedOperationInSharedContextException; +import me.devnatan.inventoryframework.ViewConfig; +import me.devnatan.inventoryframework.Viewer; +import me.devnatan.inventoryframework.component.Component; +import me.devnatan.inventoryframework.pipeline.StandardPipelinePhases; +import me.devnatan.inventoryframework.state.DefaultStateValueHost; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Unmodifiable; +import org.jetbrains.annotations.UnmodifiableView; + +abstract class AbstractIFContext extends DefaultStateValueHost implements IFContext { + + private final List components = new LinkedList<>(); + private final Deque markedForRemoval = new ArrayDeque<>(); + private final Map indexedViewers = new HashMap<>(); + protected ViewConfig config; + + @Override + public @NotNull Map getIndexedViewers() { + return indexedViewers; + } + + @Override + public final @NotNull @Unmodifiable List getViewers() { + return Collections.unmodifiableList(new ArrayList<>(getIndexedViewers().values())); + } + + @Override + public final void addViewer(@NotNull Viewer viewer) { + synchronized (getIndexedViewers()) { + getIndexedViewers().put(viewer.getId(), viewer); + } + } + + @Override + public final void removeViewer(@NotNull Viewer viewer) { + synchronized (getIndexedViewers()) { + getIndexedViewers().remove(viewer.getId()); + } + } + + @Override + public final @NotNull String getInitialTitle() { + return getConfig().getTitle().toString(); + } + + @Override + public @UnmodifiableView @NotNull List getComponents() { + return Collections.unmodifiableList(components); + } + + @Override + public Component getComponent(int position) { + for (final Component component : getComponents()) { + if (component.isContainedWithin(position)) return component; + } + return null; + } + + @Override + public void addComponent(@NotNull Component component) { + synchronized (components) { + components.add(0, component); + } + } + + @Override + public void removeComponent(@NotNull Component component) { + synchronized (components) { + components.remove(component); + } + } + + private IFSlotRenderContext createRenderContext(@NotNull Component component) { + if (!(this instanceof IFRenderContext)) + throw new InventoryFrameworkException("Slot render context cannot be created from non-render parent"); + + final IFRenderContext renderContext = (IFRenderContext) this; + return getRoot() + .getElementFactory() + .createSlotRenderContext(component.getPosition(), renderContext, renderContext.getViewer()); + } + + @Override + public void renderComponent(@NotNull Component component) { + if (!component.shouldRender(this)) { + component.setVisible(false); + + // TODO Support recursive overlapping (more than two components overlapping each other) + final Optional overlapOptional = getComponents().stream() + // FIXME This is kinda false positive needs a better explanation + .filter(Component::isVisible) + .filter(other -> other.intersects(component)) + .findFirst(); + + if (overlapOptional.isPresent()) { + final Component overlap = overlapOptional.get(); + renderComponent(overlap); + + if (overlap.isVisible()) return; + } + + component.clear(this); + return; + } + + component.render(createRenderContext(component)); + } + + @Override + public void updateComponent(@NotNull Component component) { + component.updated(createRenderContext(component)); + } + + @Override + public void update() { + getRoot().getPipeline().execute(StandardPipelinePhases.UPDATE, this); + } + + @Override + public final boolean isMarkedForRemoval(int componentIndex) { + return markedForRemoval.contains(componentIndex); + } + + @Override + public final boolean isShared() { + return getIndexedViewers().size() > 1; + } + + /** + * Throws a {@link InventoryFrameworkException} saying that the method that's being executed is + * not supported if this context is a shared context. + */ + final void tryThrowDoNotWorkWithSharedContext() { + if (!isShared()) return; + throw new UnsupportedOperationInSharedContextException(); + } + + /** + * Throws a {@link InventoryFrameworkException} saying that the method that's being executed is + * not supported if this context is a shared context, with a replacement message. + * + * @param replacement The alternative method to be used. + */ + final void tryThrowDoNotWorkWithSharedContext(String replacement) { + if (!isShared()) return; + throw new UnsupportedOperationInSharedContextException(replacement); + } + + @Override + public String toString() { + return "AbstractIFContext{" + "id=" + + getId() + ", container=" + + getContainer() + ", viewers=" + + getIndexedViewers() + ", config=" + + getConfig() + ", initialData=" + + getInitialData() + "} " + + super.toString(); + } +} diff --git a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/context/BaseViewContext.java b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/context/BaseViewContext.java deleted file mode 100644 index e6b3e64e..00000000 --- a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/context/BaseViewContext.java +++ /dev/null @@ -1,253 +0,0 @@ -package me.devnatan.inventoryframework.context; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Deque; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.UUID; -import me.devnatan.inventoryframework.RootView; -import me.devnatan.inventoryframework.ViewConfig; -import me.devnatan.inventoryframework.ViewContainer; -import me.devnatan.inventoryframework.Viewer; -import me.devnatan.inventoryframework.component.Component; -import me.devnatan.inventoryframework.pipeline.StandardPipelinePhases; -import me.devnatan.inventoryframework.state.DefaultStateValueHost; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.Unmodifiable; -import org.jetbrains.annotations.UnmodifiableView; - -class BaseViewContext extends DefaultStateValueHost implements IFContext { - - private final UUID id = UUID.randomUUID(); - private final @NotNull RootView root; - // Container can be null on pre-render/intermediate contexts - private final @Nullable ViewContainer container; - private final List components = new LinkedList<>(); - private final Deque markedForRemoval = new ArrayDeque<>(); - private final Object initialData; - protected final Map viewers; - protected ViewConfig config; - - public BaseViewContext( - @NotNull RootView root, - @Nullable ViewContainer container, - @NotNull Map viewers, - Object initialData) { - this.root = root; - this.container = container; - this.config = root.getConfig(); - this.initialData = initialData; - this.viewers = new HashMap<>(viewers); - } - - @NotNull - @Override - public UUID getId() { - return id; - } - - @Override - public @NotNull ViewConfig getConfig() { - return config; - } - - @Override - public final @NotNull RootView getRoot() { - return root; - } - - @Override - public @NotNull ViewContainer getContainer() { - if (container == null) throw new IllegalStateException("Unable to get null container"); - return container; - } - - @Override - public final @NotNull @Unmodifiable List getViewers() { - return Collections.unmodifiableList(new ArrayList<>(getIndexedViewers().values())); - } - - @Override - public @NotNull @UnmodifiableView Map getIndexedViewers() { - return Collections.unmodifiableMap(viewers); - } - - @Override - public final void addViewer(@NotNull Viewer viewer) { - synchronized (viewers) { - viewers.put(viewer.getId(), viewer); - } - } - - @Override - public final void removeViewer(@NotNull Viewer viewer) { - synchronized (viewers) { - viewers.remove(viewer.getId()); - } - } - - @Override - public @NotNull String getTitle() { - return getUpdatedTitle() == null ? getInitialTitle() : getUpdatedTitle(); - } - - @Override - public final @NotNull String getInitialTitle() { - return container.getTitle(); - } - - @Override - public final @Nullable String getUpdatedTitle() { - return getContainer().getTitle(); - } - - @Override - public final void updateTitleForEveryone(@NotNull String title) { - getContainer().changeTitle(title); - } - - @Override - public final void resetTitleForEveryone() { - getContainer().changeTitle(null); - } - - @Override - public void closeForEveryone() { - getContainer().close(); - } - - @Override - public final void openForEveryone(Class other) { - openForEveryone(other, null); - } - - @Override - public void openForEveryone(Class other, Object initialData) { - getRoot().getFramework().open(other, getViewers(), initialData); - } - - @Override - public @UnmodifiableView @NotNull List getComponents() { - return Collections.unmodifiableList(components); - } - - @Override - public Component getComponent(int position) { - for (final Component component : getComponents()) { - if (component.isContainedWithin(position)) return component; - } - return null; - } - - @Override - public void addComponent(@NotNull Component component) { - synchronized (components) { - components.add(0, component); - } - } - - @Override - public void removeComponent(@NotNull Component component) { - synchronized (components) { - components.remove(component); - } - } - - private IFSlotRenderContext createRenderContext(@NotNull Component component) { - final Viewer subject = this instanceof IFConfinedContext ? ((IFConfinedContext) this).getViewer() : null; - - return getRoot() - .getElementFactory() - .createSlotContext( - component.getPosition(), - component, - getContainer(), - subject, - getIndexedViewers(), - this, - IFSlotRenderContext.class); - } - - @Override - public void renderComponent(@NotNull Component component) { - if (!component.shouldRender(this)) { - component.setVisible(false); - - // TODO Support recursive overlapping (more than two components overlapping each other) - final Optional overlapOptional = getComponents().stream() - // FIXME This is kinda false positive needs a better explanation - .filter(Component::isVisible) - .filter(other -> other.intersects(component)) - .findFirst(); - - if (overlapOptional.isPresent()) { - final Component overlap = overlapOptional.get(); - renderComponent(overlap); - - if (overlap.isVisible()) return; - } - - component.clear(this); - return; - } - - component.render(createRenderContext(component)); - } - - @Override - public void updateComponent(@NotNull Component component) { - component.updated(createRenderContext(component)); - } - - @Override - public void update() { - getRoot().getPipeline().execute(StandardPipelinePhases.UPDATE, this); - } - - @Override - public boolean isMarkedForRemoval(int componentIndex) { - return markedForRemoval.contains(componentIndex); - } - - @Override - public Object getInitialData() { - return initialData instanceof Map ? Collections.unmodifiableMap((Map) initialData) : initialData; - } - - @Override - public boolean isShared() { - return getViewers().size() > 1; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - BaseViewContext that = (BaseViewContext) o; - return Objects.equals(getId(), that.getId()); - } - - @Override - public int hashCode() { - return Objects.hash(getId()); - } - - @Override - public String toString() { - return "BaseViewContext{" + "id=" - + id + ", container=" - + container + ", viewers=" - + viewers + ", config=" - + config + ", markedForRemoval=" - + markedForRemoval + ", initialData=" - + initialData + "} " - + super.toString(); - } -} diff --git a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/pipeline/FirstRenderInterceptor.java b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/pipeline/FirstRenderInterceptor.java index 112296b5..f7c64cb8 100644 --- a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/pipeline/FirstRenderInterceptor.java +++ b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/pipeline/FirstRenderInterceptor.java @@ -27,6 +27,8 @@ public void intercept(PipelineContext pipeline, VirtualView subject for (int i = componentList.size(); i > 0; i--) { final Component component = componentList.get(i - 1); + // TODO Setup watches on context initialization not on first render + setupWatchers(context, component); context.renderComponent(component); } } @@ -38,11 +40,7 @@ public void intercept(PipelineContext pipeline, VirtualView subject * @param context The context. */ private void registerComponents(IFRenderContext context) { - context.getComponentFactories().stream() - .map(ComponentFactory::create) - // TODO Setup watches on context initialization not on first render - .peek(component -> setupWatchers(context, component)) - .forEach(context::addComponent); + context.getComponentFactories().stream().map(ComponentFactory::create).forEach(context::addComponent); } /** diff --git a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/pipeline/ScheduledUpdateAfterCloseInterceptor.java b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/pipeline/ScheduledUpdateAfterCloseInterceptor.java index b82f4855..7fff6432 100644 --- a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/pipeline/ScheduledUpdateAfterCloseInterceptor.java +++ b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/pipeline/ScheduledUpdateAfterCloseInterceptor.java @@ -18,7 +18,8 @@ public void intercept(PipelineContext pipeline, VirtualView subject // check possible shared context viewers count first to optimize global viewers count check if (!context.getViewers().isEmpty()) return; - if (!root.getContexts().stream().allMatch(other -> other.getViewers().isEmpty())) return; + if (!root.getInternalContexts().stream() + .allMatch(other -> other.getViewers().isEmpty())) return; root.getScheduledUpdateJob().cancel(); } diff --git a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/pipeline/ScheduledUpdateAfterRenderInterceptor.java b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/pipeline/ScheduledUpdateAfterRenderInterceptor.java index 75a3a19b..ad47bc93 100644 --- a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/pipeline/ScheduledUpdateAfterRenderInterceptor.java +++ b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/pipeline/ScheduledUpdateAfterRenderInterceptor.java @@ -19,7 +19,7 @@ public void intercept(PipelineContext pipeline, VirtualView subject if (root.getScheduledUpdateJob() != null && root.getScheduledUpdateJob().isStarted()) return; final Job updateJob = root.getElementFactory().scheduleJobInterval(root, updateIntervalInTicks, () -> { - root.getContexts().forEach(IFContext::update); + root.getInternalContexts().forEach(IFContext::update); }); updateJob.start(); root.setScheduledUpdateJob(updateJob); diff --git a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/BukkitViewContainer.java b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/BukkitViewContainer.java index 910981bc..3ea995d7 100644 --- a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/BukkitViewContainer.java +++ b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/BukkitViewContainer.java @@ -1,10 +1,7 @@ package me.devnatan.inventoryframework; import java.util.ArrayList; -import java.util.Collections; -import java.util.List; import java.util.Objects; -import java.util.stream.Collectors; import me.devnatan.inventoryframework.runtime.thirdparty.InventoryUpdate; import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; @@ -14,7 +11,6 @@ import org.bukkit.inventory.PlayerInventory; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.Unmodifiable; public final class BukkitViewContainer implements ViewContainer { @@ -70,14 +66,6 @@ public int getColumnsCount() { return type.getColumns(); } - @Override - public @NotNull @Unmodifiable List getViewers() { - return Collections.unmodifiableList(new ArrayList<>(inventory.getViewers().stream() - .filter(humanEntity -> humanEntity instanceof Player) - .map(humanEntity -> new BukkitViewer((Player) humanEntity)) - .collect(Collectors.toList()))); - } - @Override public void renderItem(int slot, Object item) { requireSupportedItem(item); @@ -136,11 +124,6 @@ public int getLastSlot() { return getSlotsCount(); } - @Override - public void changeTitle(@Nullable final String title) { - for (final Viewer viewer : getViewers()) changeTitle(title, viewer); - } - @Override public void changeTitle(@Nullable String title, @NotNull Viewer target) { changeTitle(title, ((BukkitViewer) target).getPlayer()); diff --git a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/BukkitViewer.java b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/BukkitViewer.java index 29982a22..5ac23279 100644 --- a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/BukkitViewer.java +++ b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/BukkitViewer.java @@ -1,24 +1,44 @@ package me.devnatan.inventoryframework; import java.util.Objects; +import me.devnatan.inventoryframework.context.IFRenderContext; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; public final class BukkitViewer implements Viewer { private final Player player; - private ViewContainer container; + private ViewContainer selfContainer; + private IFRenderContext context; - public BukkitViewer(Player player) { + public BukkitViewer(@NotNull Player player, IFRenderContext context) { + this(player, null, context); + } + + private BukkitViewer(@NotNull Player player, @NotNull ViewContainer selfContainer, IFRenderContext context) { this.player = player; + this.selfContainer = selfContainer; + this.context = context; } public Player getPlayer() { return player; } - public ViewContainer getContainer() { - return container; + @NotNull + @Override + public IFRenderContext getContext() { + return context; + } + + @Override + public void setContext(IFRenderContext context) { + this.context = context; + } + + @Override + public Viewer withContext(IFRenderContext context) { + return new BukkitViewer(player, selfContainer, context); } @Override @@ -38,10 +58,11 @@ public void close() { @Override public @NotNull ViewContainer getSelfContainer() { - if (getContainer() == null) - container = new BukkitViewContainer(getPlayer().getInventory(), false, null); + if (selfContainer == null) + selfContainer = new BukkitViewContainer( + getPlayer().getInventory(), getContext().isShared(), ViewType.PLAYER); - return getContainer(); + return selfContainer; } @Override @@ -59,6 +80,6 @@ public int hashCode() { @Override public String toString() { - return "BukkitViewer{" + "player=" + player + ", container=" + container + '}'; + return "BukkitViewer{" + "player=" + player + ", selfContainer=" + selfContainer + '}'; } } diff --git a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/IFInventoryListener.java b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/IFInventoryListener.java index 418514c0..b2ad3f9a 100644 --- a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/IFInventoryListener.java +++ b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/IFInventoryListener.java @@ -3,18 +3,19 @@ import me.devnatan.inventoryframework.component.Component; import me.devnatan.inventoryframework.context.IFCloseContext; import me.devnatan.inventoryframework.context.IFContext; +import me.devnatan.inventoryframework.context.IFRenderContext; import me.devnatan.inventoryframework.context.IFSlotClickContext; -import me.devnatan.inventoryframework.context.SlotClickContext; -import me.devnatan.inventoryframework.internal.ElementFactory; import me.devnatan.inventoryframework.pipeline.StandardPipelinePhases; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryDragEvent; import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.event.player.PlayerPickupItemEvent; +import org.bukkit.event.server.PluginDisableEvent; import org.bukkit.inventory.PlayerInventory; final class IFInventoryListener implements Listener { @@ -25,108 +26,82 @@ public IFInventoryListener(ViewFrame viewFrame) { this.viewFrame = viewFrame; } - @SuppressWarnings("unused") - @EventHandler(ignoreCancelled = true) + @EventHandler + public void onPluginDisable(final PluginDisableEvent event) { + if (!event.getPlugin().equals(viewFrame.getOwner())) return; + + viewFrame.unregister(); + } + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public void onInventoryClick(final InventoryClickEvent event) { if (!(event.getWhoClicked() instanceof Player)) return; final Player player = (Player) event.getWhoClicked(); - final RootView root = viewFrame.getCurrentView(player); - if (root == null) return; - - final ElementFactory elementFactory = root.getElementFactory(); - final Viewer viewer = elementFactory.createViewer(player); - final IFContext mainContext; - try { - mainContext = root.getContext(viewer); - } catch (final IllegalArgumentException exception) { - event.setCancelled(true); - throw exception; - } + final Viewer viewer = viewFrame.getViewer(player); + if (viewer == null) return; - final ViewContainer container = event.getClickedInventory() instanceof PlayerInventory + final IFRenderContext context = viewer.getContext(); + final Component clickedComponent = context.getComponent(event.getRawSlot()); + if (clickedComponent == null || !clickedComponent.isVisible()) return; + + final ViewContainer clickedContainer = event.getClickedInventory() instanceof PlayerInventory ? viewer.getSelfContainer() - : mainContext.getContainer(); - final Component component = mainContext.getComponent(event.getRawSlot()); - if (component != null && !component.isVisible()) return; - - final IFSlotClickContext slotContext = new SlotClickContext( - root, - container, - viewer, - mainContext.getIndexedViewers(), - event.getRawSlot(), - mainContext, - component, - event); - - root.getPipeline().execute(StandardPipelinePhases.CLICK, slotContext); + : context.getContainer(); + final RootView root = context.getRoot(); + final IFSlotClickContext clickContext = root.getElementFactory() + .createSlotClickContext(event.getRawSlot(), viewer, clickedContainer, clickedComponent, event); + + root.getPipeline().execute(StandardPipelinePhases.CLICK, clickContext); } @SuppressWarnings("unused") - @EventHandler(ignoreCancelled = true) + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public void onInventoryClose(final InventoryCloseEvent event) { if (!(event.getPlayer() instanceof Player)) return; final Player player = (Player) event.getPlayer(); - final RootView root = viewFrame.getCurrentView(player); - if (root == null) return; + final Viewer viewer = viewFrame.getViewer(player); + if (viewer == null) return; - final ElementFactory elementFactory = root.getElementFactory(); - final String viewerIdentifier = elementFactory.convertViewer(player); - final IFContext mainContext = root.getContext(viewerIdentifier); - - final Viewer viewer = mainContext.getIndexedViewers().get(viewerIdentifier); - - final IFCloseContext closeContext = elementFactory.createContext( - root, - mainContext.getContainer(), - viewer, - mainContext.getIndexedViewers(), - IFCloseContext.class, - mainContext, - mainContext.getInitialData()); + final IFRenderContext context = viewer.getContext(); + final RootView root = context.getRoot(); + final IFCloseContext closeContext = root.getElementFactory().createCloseContext(viewer, context); root.getPipeline().execute(StandardPipelinePhases.CLOSE, closeContext); } @SuppressWarnings("deprecation") - @EventHandler(ignoreCancelled = true) + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public void onItemPickup(PlayerPickupItemEvent event) { - final Player player = event.getPlayer(); - final RootView root = viewFrame.getCurrentView(player); - if (root == null) return; - - final IFContext context = root.getContext(root.getElementFactory().convertViewer(player)); + final Viewer viewer = viewFrame.getViewer(event.getPlayer()); + if (viewer == null) return; + final IFContext context = viewer.getContext(); if (!context.getConfig().isOptionSet(ViewConfig.CANCEL_ON_PICKUP)) return; event.setCancelled(context.getConfig().getOptionValue(ViewConfig.CANCEL_ON_PICKUP)); } - @EventHandler(ignoreCancelled = true) + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public void onItemDrop(PlayerDropItemEvent event) { - final Player player = event.getPlayer(); - final RootView root = viewFrame.getCurrentView(player); - if (root == null) return; - - final IFContext context = root.getContext(root.getElementFactory().convertViewer(player)); + final Viewer viewer = viewFrame.getViewer(event.getPlayer()); + if (viewer == null) return; + final IFContext context = viewer.getContext(); if (!context.getConfig().isOptionSet(ViewConfig.CANCEL_ON_DROP)) return; event.setCancelled(context.getConfig().getOptionValue(ViewConfig.CANCEL_ON_DROP)); } - @EventHandler + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public void onInventoryDrag(InventoryDragEvent event) { if (!(event.getWhoClicked() instanceof Player)) return; - final Player player = (Player) event.getWhoClicked(); - final RootView root = viewFrame.getCurrentView(player); - if (root == null) return; - - final IFContext context = root.getContext(root.getElementFactory().convertViewer(player)); + final Viewer viewer = viewFrame.getViewer((Player) event.getWhoClicked()); + if (viewer == null) return; + final IFContext context = viewer.getContext(); if (!context.getConfig().isOptionSet(ViewConfig.CANCEL_ON_DRAG)) return; final boolean configValue = context.getConfig().getOptionValue(ViewConfig.CANCEL_ON_DRAG); diff --git a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/View.java b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/View.java index f9c5198e..5a0b10b5 100644 --- a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/View.java +++ b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/View.java @@ -26,6 +26,7 @@ @ApiStatus.OverrideOnly public class View extends PlatformView< + ViewFrame, BukkitItemComponentBuilder, Context, OpenContext, @@ -51,7 +52,7 @@ public final void registerPlatformInterceptors() { @Override public final void nextTick(Runnable task) { - Bukkit.getServer().getScheduler().runTask(((ViewFrame) getFramework()).getOwner(), task); + Bukkit.getServer().getScheduler().runTask(getFramework().getOwner(), task); } @NotNull diff --git a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/ViewFrame.java b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/ViewFrame.java index 99134489..3346b76d 100644 --- a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/ViewFrame.java +++ b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/ViewFrame.java @@ -1,14 +1,11 @@ package me.devnatan.inventoryframework; -import com.google.common.collect.Lists; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.Map; -import java.util.Set; import java.util.UUID; import java.util.function.UnaryOperator; -import java.util.stream.Collectors; import me.devnatan.inventoryframework.feature.DefaultFeatureInstaller; import me.devnatan.inventoryframework.feature.Feature; import me.devnatan.inventoryframework.feature.FeatureInstaller; @@ -16,14 +13,13 @@ import me.devnatan.inventoryframework.internal.PlatformUtils; import me.devnatan.inventoryframework.runtime.thirdparty.Metrics; import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.ServicesManager; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -public class ViewFrame extends IFViewFrame implements FeatureInstaller { +public class ViewFrame extends IFViewFrame implements FeatureInstaller { private static final String BSTATS_SYSTEM_PROP = "inventory-framework.enable-bstats"; private static final int BSTATS_PROJECT_ID = 15518; @@ -46,49 +42,28 @@ private ViewFrame(Plugin owner) { } @NotNull - public Plugin getOwner() { + public final Plugin getOwner() { return owner; } - /** - * {@inheritDoc} - *

- * This is an internal inventory-framework API that should not be used from outside of - * this library. No compatibility guarantees are provided. - */ - @ApiStatus.Internal - @Override - public void open( - @NotNull Class viewClass, @NotNull Iterable viewers, Object initialData) { - for (final Viewer viewer : viewers) - if (!(viewer instanceof BukkitViewer)) - throw new IllegalArgumentException("Only BukkitViewer viewer impl is supported"); - - final RootView view = getRegisteredViewByType(viewClass); - if (!(view instanceof PlatformView)) - throw new IllegalStateException("Only PlatformView can be opened through #open(...)"); - - view.open(Lists.newArrayList(viewers), initialData); - } - /** * Opens a view to a player. * * @param viewClass The target view to be opened. * @param player The player that the view will be open to. */ - public void open(@NotNull Class viewClass, @NotNull Player player) { + public final void open(@NotNull Class viewClass, @NotNull Player player) { open(viewClass, player, null); } /** * Opens a view to a player with initial data. * - * @param viewClass The target view to be opened. - * @param player The player that the view will be open to. + * @param viewClass The target view to be opened. + * @param player The player that the view will be open to. * @param initialData The initial data. */ - public void open(@NotNull Class viewClass, @NotNull Player player, Object initialData) { + public final void open(@NotNull Class viewClass, @NotNull Player player, Object initialData) { open(viewClass, Collections.singletonList(player), initialData); } @@ -104,7 +79,7 @@ public void open(@NotNull Class viewClass, @NotNull Player p * @param players The players that the view will be open to. */ @ApiStatus.Experimental - public void open(@NotNull Class viewClass, @NotNull Collection players) { + public final void open(@NotNull Class viewClass, @NotNull Collection players) { open(viewClass, players, null); } @@ -121,18 +96,15 @@ public void open(@NotNull Class viewClass, @NotNull Collecti * @param initialData The initial data. */ @ApiStatus.Experimental - public void open( - @NotNull Class viewClass, + public final void open( + @NotNull Class viewClass, @NotNull Collection players, Object initialData) { - final Set viewers = players.stream() - .map(player -> PlatformUtils.getFactory().createViewer(player)) - .collect(Collectors.toSet()); - open(viewClass, viewers, initialData); + internalOpen(viewClass, players, initialData); } @Override - public ViewFrame register() { + public final ViewFrame register() { if (isRegistered()) throw new IllegalStateException("This view frame is already registered"); tryEnableMetrics(); @@ -144,15 +116,15 @@ public ViewFrame register() { } @Override - public void unregister() { + public final void unregister() { if (!isRegistered()) return; // Locks new operations while unregistering setRegistered(false); - final Iterator iterator = getRegisteredViews().values().iterator(); + final Iterator iterator = registeredViews.values().iterator(); while (iterator.hasNext()) { - final RootView view = iterator.next(); + final View view = iterator.next(); try { view.closeForEveryone(); } catch (final RuntimeException ignored) { @@ -184,24 +156,21 @@ private void tryEnableMetrics() { } } - @SuppressWarnings({"rawtypes", "unchecked", "CallToPrintStackTrace"}) + @SuppressWarnings("CallToPrintStackTrace") private void initializeViews() { - for (final Map.Entry entry : getRegisteredViews().entrySet()) { - final RootView rootView = entry.getValue(); - if (!(rootView instanceof PlatformView)) - throw new IllegalStateException("Only PlatformView can be registered on this view frame"); + for (final Map.Entry entry : getRegisteredViews().entrySet()) { + final View view = entry.getValue(); - final PlatformView platformView = (PlatformView) rootView; try { - platformView.internalInitialization(this); - platformView.setInitialized(true); + view.internalInitialization(this); + view.setInitialized(true); } catch (final RuntimeException exception) { - platformView.setInitialized(false); + view.setInitialized(false); getOwner() .getLogger() .severe(String.format( "An error occurred while enabling view %s: %s", - rootView.getClass().getName(), exception)); + view.getClass().getName(), exception)); exception.printStackTrace(); } } @@ -227,22 +196,12 @@ private void checkRelocationIssues() { } /** - * Returns the current {@link RootView} the player is viewing based on open inventory. - *

- * Only views registered in that view frame are returned. - * - * @param player The player. - * @return The current view the player is viewing or {@code null} if it was not found, or it was - * not possible to determine it. + * This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. */ - public RootView getCurrentView(@NotNull Player player) { - final Inventory topInventory = player.getOpenInventory().getTopInventory(); - if (!(topInventory.getHolder() instanceof View)) return null; - - final View view = (View) topInventory.getHolder(); - if (!getRegisteredViews().containsKey(view.getUniqueId())) return null; - - return view; + @ApiStatus.Internal + public final Viewer getViewer(@NotNull Player player) { + return viewerById.get(player.getUniqueId().toString()); } /** @@ -256,22 +215,23 @@ public RootView getCurrentView(@NotNull Player player) { } @Override - public @NotNull ViewFrame getPlatform() { + public final @NotNull ViewFrame getPlatform() { return this; } @Override - public Collection> getInstalledFeatures() { + public final Collection> getInstalledFeatures() { return featureInstaller.getInstalledFeatures(); } @Override - public @NotNull R install(@NotNull Feature feature, @NotNull UnaryOperator configure) { + public final @NotNull R install( + @NotNull Feature feature, @NotNull UnaryOperator configure) { return featureInstaller.install(feature, configure); } @Override - public void uninstall(@NotNull Feature feature) { + public final void uninstall(@NotNull Feature feature) { featureInstaller.uninstall(feature); } } diff --git a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/CloseContext.java b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/CloseContext.java index f9339d8f..6fa06974 100644 --- a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/CloseContext.java +++ b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/CloseContext.java @@ -1,65 +1,35 @@ package me.devnatan.inventoryframework.context; -import java.util.List; -import java.util.Map; -import java.util.Objects; import java.util.UUID; -import java.util.stream.Collectors; -import me.devnatan.inventoryframework.BukkitViewContainer; import me.devnatan.inventoryframework.BukkitViewer; -import me.devnatan.inventoryframework.RootView; +import me.devnatan.inventoryframework.View; import me.devnatan.inventoryframework.ViewConfig; import me.devnatan.inventoryframework.ViewContainer; import me.devnatan.inventoryframework.Viewer; import org.bukkit.entity.Player; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.UnmodifiableView; -public final class CloseContext extends ConfinedContext implements IFCloseContext, Context { +public class CloseContext extends PlatformConfinedContext implements IFCloseContext { - private final IFContext parent; + private final Viewer subject; private final Player player; + private final IFRenderContext parent; + private boolean cancelled; - public CloseContext( - @NotNull RootView root, - @NotNull ViewContainer container, - Viewer subject, - @NotNull Map viewers, - @NotNull IFContext parent) { - super(root, container, subject, viewers, parent.getInitialData()); + @ApiStatus.Internal + public CloseContext(@NotNull Viewer subject, @NotNull IFRenderContext parent) { + this.subject = subject; this.player = ((BukkitViewer) subject).getPlayer(); this.parent = parent; } - public IFContext getParent() { - return parent; - } - - @NotNull - @Override - public Player getPlayer() { + // TODO Needs documentation + public final @NotNull Player getPlayer() { return player; } - @Override - public @UnmodifiableView List getAllPlayers() { - return getViewers().stream() - .map(viewer -> (BukkitViewer) viewer) - .map(BukkitViewer::getPlayer) - .collect(Collectors.toList()); - } - - @Override - public void updateTitleForPlayer(@NotNull String title, @NotNull Player player) { - ((BukkitViewContainer) getContainer()).changeTitle(title, player); - } - - @Override - public void resetTitleForPlayer(@NotNull Player player) { - ((BukkitViewContainer) getContainer()).changeTitle(null, player); - } - @Override public boolean isCancelled() { return cancelled; @@ -71,43 +41,37 @@ public void setCancelled(boolean cancelled) { } @Override - public @NotNull UUID getId() { - return getParent().getId(); + public final @NotNull Viewer getViewer() { + return subject; } @Override - public @NotNull ViewConfig getConfig() { - return getParent().getConfig(); + public final RenderContext getParent() { + return (RenderContext) parent; } @Override - public void closeForEveryone() {} + public final @NotNull UUID getId() { + return getParent().getId(); + } @Override - public void closeForPlayer() {} + public final @NotNull ViewConfig getConfig() { + return getParent().getConfig(); + } @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - CloseContext that = (CloseContext) o; - return isCancelled() == that.isCancelled() - && Objects.equals(getParent(), that.getParent()) - && Objects.equals(getPlayer(), that.getPlayer()); + public final ViewContainer getContainer() { + return getParent().getContainer(); } @Override - public int hashCode() { - return Objects.hash(super.hashCode(), getParent(), getPlayer(), isCancelled()); + public final @NotNull View getRoot() { + return getParent().getRoot(); } @Override - public String toString() { - return "CloseContext{" + "parent=" - + parent + ", player=" - + player + ", cancelled=" - + cancelled + "} " - + super.toString(); + public final Object getInitialData() { + return getParent().getInitialData(); } } diff --git a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/Context.java b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/Context.java index 2bbedc26..a89115d4 100644 --- a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/Context.java +++ b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/Context.java @@ -4,7 +4,6 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.UnmodifiableView; public interface Context extends IFContext { @@ -16,15 +15,14 @@ public interface Context extends IFContext { * * @return A player in this interaction context. */ - @NotNull - Player getPlayer(); + // @UnknownNullability + // Player getPlayer(); /** *

This API is experimental and is not subject to the general compatibility guarantees * such API may be changed or may be removed completely in any further release. */ @ApiStatus.Experimental - @UnmodifiableView List getAllPlayers(); /** diff --git a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/OpenContext.java b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/OpenContext.java index 95705e44..feebd8b9 100644 --- a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/OpenContext.java +++ b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/OpenContext.java @@ -1,163 +1,142 @@ package me.devnatan.inventoryframework.context; -import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.UUID; import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; import me.devnatan.inventoryframework.BukkitViewer; import me.devnatan.inventoryframework.InventoryFrameworkException; -import me.devnatan.inventoryframework.RootView; +import me.devnatan.inventoryframework.UnsupportedOperationInSharedContextException; +import me.devnatan.inventoryframework.View; import me.devnatan.inventoryframework.ViewConfig; import me.devnatan.inventoryframework.ViewConfigBuilder; +import me.devnatan.inventoryframework.ViewContainer; import me.devnatan.inventoryframework.Viewer; import org.bukkit.entity.Player; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.UnmodifiableView; +import org.jetbrains.annotations.Nullable; -public class OpenContext extends ConfinedContext implements IFOpenContext, Context { +public class OpenContext extends PlatformContext implements IFOpenContext { - private final Player player; - private boolean cancelled; + // --- Inherited --- + private final UUID id; + private final View root; + private final Viewer subject; + private final Object initialData; + private final Map viewers; + + // --- User Provided --- private CompletableFuture waitTask; private ViewConfigBuilder inheritedConfigBuilder; + // --- Properties --- + private final Player player; + private boolean cancelled = false; + + /** + * Creates a new open context instance. + *

+ * This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. + * + * @param root Root view that will be owner of the upcoming render context. + * @param subject The viewer that is opening the view. + * @param viewers Who'll be the viewers of this context, if this parameter is provided it + * means that this context is a shared context. + * Must be provided even in non-shared context cases. + * @param initialData Initial data provided by the user. + */ @ApiStatus.Internal public OpenContext( - @NotNull RootView root, Viewer subject, @NotNull Map viewers, Object initialData) { - super(root, null, subject, viewers, initialData); - this.player = subject != null ? ((BukkitViewer) subject).getPlayer() : null; + @NotNull View root, @Nullable Viewer subject, @NotNull Map viewers, Object initialData) { + this.id = UUID.randomUUID(); + this.subject = subject; + this.root = root; + this.viewers = viewers; + this.initialData = initialData; + this.player = subject == null ? null : ((BukkitViewer) subject).getPlayer(); + } + + /** + * The player that's currently opening the view. + * + * @return The player that is opening the view. + * @throws UnsupportedOperationInSharedContextException If this context {@link #isShared() is shared}. + */ + public final @NotNull Player getPlayer() { + tryThrowDoNotWorkWithSharedContext("getAllPlayers()"); + return player; } @Override - public boolean isCancelled() { + public final boolean isCancelled() { return cancelled; } @Override - public void setCancelled(boolean cancelled) { + public final void setCancelled(boolean cancelled) { this.cancelled = cancelled; } - private ViewConfigBuilder getInheritedConfigBuilder() { - return inheritedConfigBuilder; - } - - public void setInheritedConfigBuilder(ViewConfigBuilder inheritedConfigBuilder) { - this.inheritedConfigBuilder = inheritedConfigBuilder; - } - @Override - public void closeForEveryone() { - unsupportedOperation("#setCancelled(true)"); - } - - @Override - public void closeForPlayer() { - unsupportedOperation("#setCancelled(true)"); - } - - @Override - public void openForPlayer(@NotNull Class other) { - unsupportedOperation(); - } - - @Override - public void openForPlayer(@NotNull Class other, Object initialData) { - unsupportedOperation(); - } - - @Override - public void resetTitleForPlayer() { - unsupportedOperation(); + public final CompletableFuture getAsyncOpenJob() { + return waitTask; } @Override - public void updateTitleForPlayer(@NotNull String title) { - unsupportedOperation(); + public final @NotNull View getRoot() { + return root; } @Override - public void updateTitleForPlayer(@NotNull String title, @NotNull Player player) { - unsupportedOperation(); + public final @NotNull Map getIndexedViewers() { + return viewers; } @Override - public void resetTitleForPlayer(@NotNull Player player) { - unsupportedOperation(); + public final @NotNull UUID getId() { + return id; } @Override - public CompletableFuture getAsyncOpenJob() { - return waitTask; + public final Object getInitialData() { + return initialData; } @Override - public @NotNull Player getPlayer() { - return player; + public final Viewer getSubject() { + return subject; } @Override - public @UnmodifiableView List getAllPlayers() { - return getViewers().stream() - .map(viewer -> (BukkitViewer) viewer) - .map(BukkitViewer::getPlayer) - .collect(Collectors.toList()); + public final ViewContainer getContainer() { + throw new InventoryFrameworkException("Container is not available in open phase"); } @Override - public void waitUntil(@NotNull CompletableFuture task) { + public final void waitUntil(@NotNull CompletableFuture task) { this.waitTask = task; } @Override - public @NotNull ViewConfig getConfig() { + public final @NotNull ViewConfig getConfig() { return inheritedConfigBuilder == null - ? super.getConfig() - : inheritedConfigBuilder.build().merge(getRoot().getConfig()); + ? getRoot().getConfig() + : Objects.requireNonNull(getModifiedConfig(), "Modified config cannot be null"); } @Override - public @NotNull ViewConfigBuilder modifyConfig() { - if (inheritedConfigBuilder == null) inheritedConfigBuilder = new ViewConfigBuilder(); - - return inheritedConfigBuilder; - } + public final ViewConfig getModifiedConfig() { + if (inheritedConfigBuilder == null) return null; - private void unsupportedOperation() { - throw new InventoryFrameworkException( - new IllegalStateException("This operation cannot be called on open handler.")); - } - - private void unsupportedOperation(String usage) { - throw new InventoryFrameworkException(new IllegalStateException( - String.format("This operation cannot be called on open handler. Use %s instead.", usage))); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - OpenContext that = (OpenContext) o; - return isCancelled() == that.isCancelled() - && Objects.equals(getPlayer(), that.getPlayer()) - && Objects.equals(getInheritedConfigBuilder(), that.getInheritedConfigBuilder()); + return inheritedConfigBuilder.build().merge(getRoot().getConfig()); } @Override - public int hashCode() { - return Objects.hash(super.hashCode(), getPlayer(), isCancelled(), getInheritedConfigBuilder()); - } + public final @NotNull ViewConfigBuilder modifyConfig() { + if (inheritedConfigBuilder == null) inheritedConfigBuilder = new ViewConfigBuilder(); - @Override - public String toString() { - return "OpenContext{" + "player=" - + player + ", cancelled=" - + cancelled + ", waitTask=" - + waitTask + ", inheritedConfigBuilder=" - + inheritedConfigBuilder + "} " - + super.toString(); + return inheritedConfigBuilder; } } diff --git a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/RenderContext.java b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/RenderContext.java index 732e2d0e..8b800db2 100644 --- a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/RenderContext.java +++ b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/RenderContext.java @@ -7,7 +7,7 @@ import java.util.stream.Collectors; import me.devnatan.inventoryframework.BukkitViewContainer; import me.devnatan.inventoryframework.BukkitViewer; -import me.devnatan.inventoryframework.RootView; +import me.devnatan.inventoryframework.View; import me.devnatan.inventoryframework.ViewConfig; import me.devnatan.inventoryframework.ViewContainer; import me.devnatan.inventoryframework.Viewer; @@ -18,31 +18,40 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public final class RenderContext extends PlatformRenderContext implements Context { +public class RenderContext extends PlatformRenderContext implements Context { private final Player player; + /** + * This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. + */ @ApiStatus.Internal public RenderContext( - UUID id, - RootView root, - ViewContainer container, + @NotNull UUID id, + @NotNull View root, + @NotNull ViewConfig config, + @NotNull ViewContainer container, + @NotNull Map viewers, Viewer subject, - Map viewers, - ViewConfig config, Object initialData) { - super(id, root, container, subject, viewers, config, initialData); + super(id, root, config, container, viewers, subject, initialData); this.player = subject != null ? ((BukkitViewer) subject).getPlayer() : null; } - @NotNull @Override - public Player getPlayer() { + public final @NotNull View getRoot() { + return (View) root; + } + + // TODO documentation + public final @NotNull Player getPlayer() { + tryThrowDoNotWorkWithSharedContext("getAllPlayers"); return player; } @Override - public List getAllPlayers() { + public final List getAllPlayers() { return getViewers().stream() .map(viewer -> (BukkitViewer) viewer) .map(BukkitViewer::getPlayer) diff --git a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/SlotClickContext.java b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/SlotClickContext.java index 07af0bb6..6f80ed0c 100644 --- a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/SlotClickContext.java +++ b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/SlotClickContext.java @@ -1,6 +1,5 @@ package me.devnatan.inventoryframework.context; -import java.util.Map; import me.devnatan.inventoryframework.RootView; import me.devnatan.inventoryframework.ViewContainer; import me.devnatan.inventoryframework.Viewer; @@ -11,40 +10,38 @@ import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public final class SlotClickContext extends SlotContext implements IFSlotClickContext { +public class SlotClickContext extends SlotContext implements IFSlotClickContext { + private final Viewer whoClicked; + private final ViewContainer clickedContainer; + private final Component clickedComponent; private final InventoryClickEvent clickOrigin; private boolean cancelled; + @ApiStatus.Internal public SlotClickContext( - @NotNull RootView root, - @NotNull ViewContainer container, - Viewer subject, - @NotNull Map viewers, int slot, - @NotNull IFContext parent, - @Nullable Component component, + @NotNull IFRenderContext parent, + @NotNull Viewer whoClicked, + @NotNull ViewContainer clickedContainer, + @Nullable Component clickedComponent, @NotNull InventoryClickEvent clickOrigin) { - super(root, container, subject, viewers, slot, parent, component); + super(slot, parent); + this.whoClicked = whoClicked; + this.clickedContainer = clickedContainer; + this.clickedComponent = clickedComponent; this.clickOrigin = clickOrigin; } - @Override - public boolean isCancelled() { - return cancelled; - } - - @Override - public void setCancelled(boolean cancelled) { - this.cancelled = cancelled; - } - - @Override - public int getClickedSlot() { - return clickOrigin.getRawSlot(); + /** + * The player who clicked on the slot. + */ + public final @NotNull Player getPlayer() { + return (Player) clickOrigin.getWhoClicked(); } /** @@ -59,67 +56,106 @@ public InventoryClickEvent getClickOrigin() { } /** - * The player who clicked on the slot. + * The item that was clicked. */ @Override - public @NotNull Player getPlayer() { - return (Player) clickOrigin.getWhoClicked(); + public final ItemStack getItem() { + return clickOrigin.getCurrentItem(); } - /** - * The item that was clicked. - */ @Override - public ItemStack getItem() { - return clickOrigin.getCurrentItem(); + public final Component getComponent() { + return clickedComponent; + } + + @Override + public final @NotNull ViewContainer getClickedContainer() { + return clickedContainer; + } + + @Override + public final boolean isCancelled() { + return cancelled; + } + + @Override + public final void setCancelled(boolean cancelled) { + this.cancelled = cancelled; } @Override - public boolean isLeftClick() { + public final int getClickedSlot() { + return clickOrigin.getRawSlot(); + } + + @Override + public final boolean isLeftClick() { return getClickOrigin().isLeftClick(); } @Override - public boolean isRightClick() { + public final boolean isRightClick() { return getClickOrigin().isRightClick(); } @Override - public boolean isMiddleClick() { + public final boolean isMiddleClick() { return getClickOrigin().getClick() == ClickType.MIDDLE; } @Override - public boolean isShiftClick() { + public final boolean isShiftClick() { return getClickOrigin().isShiftClick(); } @Override - public boolean isKeyboardClick() { + public final boolean isKeyboardClick() { return getClickOrigin().getClick().isKeyboardClick(); } @Override - public boolean isOutsideClick() { + public final boolean isOutsideClick() { return getClickOrigin().getSlotType() == InventoryType.SlotType.OUTSIDE; } @Override @NotNull - public String getClickIdentifier() { + public final String getClickIdentifier() { return getClickOrigin().getClick().name(); } @Override - public boolean isOnEntityContainer() { + public final boolean isOnEntityContainer() { return getClickOrigin().getClickedInventory() instanceof PlayerInventory; } @Override - public String toString() { - return "SlotClickContext{" + "clickOrigin=" - + clickOrigin + ", cancelled=" - + cancelled + "} " - + super.toString(); + public final Viewer getViewer() { + return whoClicked; + } + + @Override + public final void closeForPlayer() { + getParent().closeForPlayer(); + } + + @Override + public final void openForPlayer(@NotNull Class other) { + getParent().openForPlayer(other); + } + + @Override + public final void openForPlayer(@NotNull Class other, Object initialData) { + getParent().openForPlayer(other, initialData); + } + + @Override + public final void updateTitleForPlayer(@NotNull String title) { + getParent().updateTitleForPlayer(title); + } + + @Override + public final void resetTitleForPlayer() { + getParent().resetTitleForPlayer(); } } diff --git a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/SlotContext.java b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/SlotContext.java index 56923a19..a8806a99 100644 --- a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/SlotContext.java +++ b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/SlotContext.java @@ -3,10 +3,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; -import java.util.stream.Collectors; -import me.devnatan.inventoryframework.BukkitViewContainer; -import me.devnatan.inventoryframework.BukkitViewer; -import me.devnatan.inventoryframework.RootView; +import me.devnatan.inventoryframework.View; import me.devnatan.inventoryframework.ViewConfig; import me.devnatan.inventoryframework.ViewContainer; import me.devnatan.inventoryframework.Viewer; @@ -17,54 +14,26 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnmodifiableView; -public class SlotContext extends ConfinedContext implements IFSlotContext, Context { +public abstract class SlotContext extends PlatformContext implements IFSlotContext, Context { + // --- Inherited --- + private final IFRenderContext parent; + + // --- Properties --- private int slot; - private final Player player; - private final IFContext parent; - private final Component component; - - public SlotContext( - @NotNull RootView root, - @NotNull ViewContainer container, - Viewer subject, - @NotNull Map viewers, - int slot, - @NotNull IFContext parent, - @Nullable Component component) { - super(root, container, subject, viewers, parent.getInitialData()); + + protected SlotContext(int slot, @NotNull IFRenderContext parent) { this.slot = slot; - this.player = subject == null ? null : ((BukkitViewer) subject).getPlayer(); this.parent = parent; - this.component = component; } - @Override - public void update() { - getParent().update(); - } - - @Override - public @NotNull ViewContainer getContainer() { - return getParent().getContainer(); - } + public abstract ItemStack getItem(); @Override - public @NotNull @UnmodifiableView Map getIndexedViewers() { - return getParent().getIndexedViewers(); - } - - @Override - public final @NotNull UUID getId() { - return getParent().getId(); - } - - @Override - public final @NotNull ViewConfig getConfig() { - return getParent().getConfig(); + public final @NotNull RenderContext getParent() { + return (RenderContext) parent; } @Override @@ -73,130 +42,118 @@ public final int getSlot() { } @Override - public void setSlot(int slot) { + public final void setSlot(int slot) { this.slot = slot; } @Override - public boolean isOnEntityContainer() { - return getContainer().isEntityContainer(); - } - - @Override - public boolean hasChanged() { + public final void clear() { + // TODO do something throw new UnsupportedOperationException(); } @Override - public void setChanged(boolean changed) { - throw new UnsupportedOperationException(); + public final @NotNull Map getIndexedViewers() { + return getParent().getIndexedViewers(); } @Override - public boolean isRegistered() { - return false; + public final @NotNull String getTitle() { + return getParent().getTitle(); } @Override - public final @NotNull IFContext getParent() { - return parent; + public final @UnmodifiableView @NotNull List getComponents() { + return getParent().getComponents(); } @Override - public void clear() { - throw new UnsupportedOperationException(); + public final Component getComponent(int position) { + return getParent().getComponent(position); } - @Nullable @Override - public Component getComponent() { - return component; + public final void addComponent(@NotNull Component component) { + getParent().addComponent(component); } @Override - public @NotNull Player getPlayer() { - return player; + public final void removeComponent(@NotNull Component component) { + getParent().removeComponent(component); } @Override - public @UnmodifiableView List getAllPlayers() { - return getViewers().stream() - .map(viewer -> (BukkitViewer) viewer) - .map(BukkitViewer::getPlayer) - .collect(Collectors.toList()); + public final void renderComponent(@NotNull Component component) { + getParent().renderComponent(component); } @Override - public void updateTitleForPlayer(@NotNull String title, @NotNull Player player) { - ((BukkitViewContainer) getContainer()).changeTitle(title, player); + public final void updateComponent(@NotNull Component component) { + getParent().updateComponent(component); } @Override - public void resetTitleForPlayer(@NotNull Player player) { - ((BukkitViewContainer) getContainer()).changeTitle(null, player); + public final void update() { + getParent().update(); } - public ItemStack getItem() { - return ((BukkitViewContainer) getContainer()).getInventory().getItem(getSlot()); + @Override + public final Object getState(State state) { + return getParent().getState(state); } @Override - public final @UnmodifiableView @NotNull List getComponents() { - return getParent().getComponents(); + public final void initializeState(long id, @NotNull StateValue value) { + getParent().initializeState(id, value); } @Override - public final void addComponent(@NotNull Component component) { - throw new UnsupportedOperationException("Slot context do not have components"); + public final void updateState(long id, Object value) { + getParent().updateState(id, value); } @Override - public final void removeComponent(@NotNull Component component) { - throw new UnsupportedOperationException("Slot context do not have components"); + public final void watchState(long id, StateWatcher listener) { + getParent().watchState(id, listener); } @Override - public void renderComponent(@NotNull Component component) { - throw new UnsupportedOperationException("Slot context do not have components"); + public final @NotNull UUID getId() { + return getParent().getId(); } @Override - public void updateComponent(@NotNull Component component) { - throw new UnsupportedOperationException("Slot context do not have components"); + public final @NotNull ViewConfig getConfig() { + return getParent().getConfig(); } @Override - public boolean isMarkedForRemoval(int componentIndex) { - return getParent().isMarkedForRemoval(componentIndex); + public final ViewContainer getContainer() { + return getParent().getContainer(); } @Override - public Object getState(State state) { - return getParent().getState(state); + public final @NotNull View getRoot() { + return getParent().getRoot(); } @Override - public void initializeState(long id, @NotNull StateValue value) { - getParent().initializeState(id, value); + public final Object getInitialData() { + return getParent().getInitialData(); } @Override - public void updateState(long id, Object value) { - getParent().updateState(id, value); + public List getAllPlayers() { + return getParent().getAllPlayers(); } @Override - public void watchState(long id, StateWatcher listener) { - getParent().watchState(id, listener); + public void updateTitleForPlayer(@NotNull String title, @NotNull Player player) { + getParent().updateTitleForPlayer(title, player); } @Override - public String toString() { - return "SlotContext{" + "slot=" - + slot + ", player=" - + player + ", parent=" - + parent + ", component=" - + component + "} " - + super.toString(); + public void resetTitleForPlayer(@NotNull Player player) { + getParent().resetTitleForPlayer(player); } } diff --git a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/SlotRenderContext.java b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/SlotRenderContext.java index 06a4eb38..dc58ee3a 100644 --- a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/SlotRenderContext.java +++ b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/context/SlotRenderContext.java @@ -1,56 +1,92 @@ package me.devnatan.inventoryframework.context; -import java.util.Map; import me.devnatan.inventoryframework.RootView; -import me.devnatan.inventoryframework.ViewContainer; import me.devnatan.inventoryframework.Viewer; -import me.devnatan.inventoryframework.component.Component; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public final class SlotRenderContext extends SlotContext implements IFSlotRenderContext { +public class SlotRenderContext extends SlotContext implements IFSlotRenderContext { + + private final Viewer viewer; private ItemStack item; private boolean cancelled; + private boolean changed; - public SlotRenderContext( - @NotNull RootView root, - @NotNull ViewContainer container, - Viewer subject, - @NotNull Map viewers, - int slot, - @NotNull IFContext parent, - @Nullable Component component) { - super(root, container, subject, viewers, slot, parent, component); + @ApiStatus.Internal + public SlotRenderContext(int slot, @NotNull IFRenderContext parent, @Nullable Viewer viewer) { + super(slot, parent); + this.viewer = viewer; } @Override - public Object getResult() { + public final Object getResult() { return item; } @Override - public ItemStack getItem() { + public final ItemStack getItem() { return item; } - public void setItem(ItemStack item) { + public final void setItem(ItemStack item) { this.item = item; } @Override - public boolean isCancelled() { + public final boolean isCancelled() { return cancelled; } @Override - public void setCancelled(boolean cancelled) { + public final void setCancelled(boolean cancelled) { this.cancelled = cancelled; } @Override - public String toString() { - return "SlotRenderContext{" + "item=" + item + ", cancelled=" + cancelled + "} " + super.toString(); + public final boolean hasChanged() { + return changed; + } + + @Override + public final void setChanged(boolean changed) { + this.changed = changed; + } + + @Override + public final boolean isOnEntityContainer() { + return getContainer().isEntityContainer(); + } + + @Override + public final Viewer getViewer() { + return viewer; + } + + @Override + public final void closeForPlayer() { + getParent().closeForPlayer(); + } + + @Override + public final void openForPlayer(@NotNull Class other) { + getParent().openForPlayer(other); + } + + @Override + public final void openForPlayer(@NotNull Class other, Object initialData) { + getParent().openForPlayer(other, initialData); + } + + @Override + public final void updateTitleForPlayer(@NotNull String title) { + getParent().updateTitleForPlayer(title); + } + + @Override + public final void resetTitleForPlayer() { + getParent().resetTitleForPlayer(); } } diff --git a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/internal/BukkitElementFactory.java b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/internal/BukkitElementFactory.java index ae790c80..f6e4797d 100644 --- a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/internal/BukkitElementFactory.java +++ b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/internal/BukkitElementFactory.java @@ -1,11 +1,12 @@ package me.devnatan.inventoryframework.internal; -import static java.util.Objects.requireNonNull; import static me.devnatan.inventoryframework.runtime.util.InventoryUtils.checkInventoryTypeSupport; -import static me.devnatan.inventoryframework.util.IsTypeOf.isTypeOf; +import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; import me.devnatan.inventoryframework.*; import me.devnatan.inventoryframework.component.BukkitItemComponentBuilder; import me.devnatan.inventoryframework.component.Component; @@ -13,8 +14,8 @@ import me.devnatan.inventoryframework.context.*; import me.devnatan.inventoryframework.logging.Logger; import me.devnatan.inventoryframework.logging.NoopLogger; -import org.bukkit.entity.Entity; import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import org.jetbrains.annotations.NotNull; @@ -57,63 +58,56 @@ public class BukkitElementFactory extends ElementFactory { } @Override - public @NotNull Viewer createViewer(Object... parameters) { - final Object playerObject = parameters[0]; - if (!(playerObject instanceof Player)) + public @NotNull Viewer createViewer(@NotNull Object entity, IFRenderContext context) { + if (!(entity instanceof Player)) throw new IllegalArgumentException("createViewer(...) first parameter must be a Player"); - return new BukkitViewer((Player) playerObject); + return new BukkitViewer((Player) entity, context); } @Override - public @NotNull String convertViewer(Object input) { - if (input instanceof String) return UUID.fromString((String) input).toString(); - if (input instanceof UUID) return input.toString(); - if (input instanceof Entity) return ((Entity) input).getUniqueId().toString(); - - throw new IllegalArgumentException("Inconvertible viewer id: " + input); + public IFOpenContext createOpenContext( + @NotNull RootView root, @Nullable Viewer subject, @NotNull List viewers, Object initialData) { + return new OpenContext( + (View) root, + subject, + viewers.stream().collect(Collectors.toMap(Viewer::getId, Function.identity())), + initialData); } - @SuppressWarnings("unchecked") @Override - public @NotNull T createContext( + public IFRenderContext createRenderContext( + @NotNull UUID id, @NotNull RootView root, - ViewContainer container, - Viewer subject, + @NotNull ViewConfig config, + @NotNull ViewContainer container, @NotNull Map viewers, - @NotNull Class kind, - @Nullable IFContext parent, + Viewer subject, Object initialData) { - if (isTypeOf(IFOpenContext.class, kind)) return (T) new OpenContext(root, subject, viewers, initialData); - if (isTypeOf(IFRenderContext.class, kind)) - return (T) new me.devnatan.inventoryframework.context.RenderContext( - requireNonNull(parent).getId(), - root, - container, - subject, - viewers, - requireNonNull(parent).getConfig(), - initialData); - if (isTypeOf(IFCloseContext.class, kind)) - return (T) new CloseContext(root, container, subject, viewers, requireNonNull(parent)); - - throw new UnsupportedOperationException("Unsupported context kind: " + kind); + return new RenderContext(id, (View) root, config, container, viewers, subject, initialData); } - @SuppressWarnings("unchecked") @Override - public @NotNull T createSlotContext( - int slot, - Component component, - @NotNull ViewContainer container, - Viewer subject, - @NotNull Map viewers, - @NotNull IFContext parent, - @NotNull Class kind) { - if (isTypeOf(IFSlotRenderContext.class, kind)) - return (T) new SlotRenderContext(parent.getRoot(), container, subject, viewers, slot, parent, component); + public IFSlotClickContext createSlotClickContext( + int slotClicked, + @NotNull Viewer whoClicked, + @NotNull ViewContainer interactionContainer, + @Nullable Component componentClicked, + @NotNull Object origin) { + final IFRenderContext context = whoClicked.getContext(); + return new SlotClickContext( + slotClicked, context, whoClicked, interactionContainer, componentClicked, (InventoryClickEvent) origin); + } + + @Override + public IFSlotRenderContext createSlotRenderContext( + int slot, @NotNull IFRenderContext parent, @Nullable Viewer viewer) { + return new SlotRenderContext(slot, parent, viewer); + } - return (T) new SlotContext(parent.getRoot(), container, subject, viewers, slot, parent, component); + @Override + public IFCloseContext createCloseContext(@NotNull Viewer viewer, @NotNull IFRenderContext parent) { + return new CloseContext(viewer, parent); } @Override diff --git a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/runtime/InventoryFramework.java b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/runtime/InventoryFramework.java index 6cec7fe2..de68108a 100644 --- a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/runtime/InventoryFramework.java +++ b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/runtime/InventoryFramework.java @@ -2,7 +2,6 @@ import org.bukkit.plugin.java.JavaPlugin; -@SuppressWarnings("unused") public final class InventoryFramework extends JavaPlugin { public static final String LIBRARY_VERSION = "3.0.0-rc.2"; diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/IFViewFrame.java b/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/IFViewFrame.java similarity index 55% rename from inventory-framework-api/src/main/java/me/devnatan/inventoryframework/IFViewFrame.java rename to inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/IFViewFrame.java index e28099cc..ca24ddc8 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/IFViewFrame.java +++ b/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/IFViewFrame.java @@ -1,13 +1,17 @@ package me.devnatan.inventoryframework; import java.util.*; +import java.util.stream.Collectors; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.UnmodifiableView; -public abstract class IFViewFrame> { +abstract class IFViewFrame, V extends PlatformView> { private boolean registered; - private final Map registeredViews = new HashMap<>(); + protected final Map registeredViews = new HashMap<>(); + protected final Map viewerById = new HashMap<>(); protected IFViewFrame() {} @@ -16,8 +20,8 @@ protected IFViewFrame() {} * * @return A Map containing all registered views in this view frame. */ - protected final @NotNull Map getRegisteredViews() { - return registeredViews; + protected final @NotNull @UnmodifiableView Map getRegisteredViews() { + return Collections.unmodifiableMap(registeredViews); } /** @@ -27,11 +31,12 @@ protected IFViewFrame() {} * @return An RootView instance of the given type. * @throws IllegalArgumentException If the given cannot be found. */ - protected final @NotNull RootView getRegisteredViewByType(@NotNull Class type) { + public final @NotNull V getRegisteredViewByType(@NotNull Class type) { return getRegisteredViews().values().stream() .filter(view -> view.getClass().equals(type)) .findFirst() - .orElseThrow(() -> new IllegalArgumentException(String.format("Unknown view: %s", type))); + .orElseThrow(() -> + new IllegalArgumentException(String.format("View not found or not registered: %s", type))); } /** @@ -42,9 +47,9 @@ protected IFViewFrame() {} */ @SuppressWarnings("unchecked") @Contract("_ -> this") - public final S with(RootView... views) { + public final S with(V... views) { synchronized (registeredViews) { - for (final RootView view : views) { + for (final V view : views) { if (registeredViews.containsKey(view.getUniqueId())) throw new IllegalStateException(String.format( "View %s already registered. Maybe your are using #register() before #with(...).", @@ -61,9 +66,9 @@ public final S with(RootView... views) { * * @param views The views that'll be removed. */ - public final void remove(RootView... views) { + public final void remove(V... views) { synchronized (registeredViews) { - for (final RootView view : views) { + for (final V view : views) { view.closeForEveryone(); registeredViews.remove(view.getUniqueId()); } @@ -83,17 +88,6 @@ public final void remove(RootView... views) { */ public abstract void unregister(); - /** - * Opens a view to a {@link Viewer}. - * - * @param viewClass The target view to be open. - * @param viewers The viewers that the view will be open to. They will share the same context. - * @param initialData The initial data. - * @throws IllegalStateException If this view is not registered in this view frame. - */ - public abstract void open( - @NotNull Class viewClass, @NotNull Iterable viewers, Object initialData); - /** * If this view frame is registered. * @@ -113,16 +107,37 @@ protected final void setRegistered(boolean registered) { } /** - * Throws an {@link IllegalStateException} if this framework is already registered when trying - * to install a feature. + * Opens a view to more than one player with initial data. + *

+ * These players will see the same inventory and share the same context. + * + *

This API is experimental and is not subject to the general compatibility guarantees + * such API may be changed or may be removed completely in any further release. * - * @param featureName The feature name. - * @param featureAccessName The feature installation constant name. + * @param viewClass The target view to be opened. + * @param players The players that the view will be open to. + * @param initialData The initial data. */ - public final void checkNotRegisteredForFeatureInstall(String featureName, String featureAccessName) { - if (!isRegistered()) return; - throw new IllegalStateException(String.format( - "Framework must be unregistered to install %s feature. Call #install(%s) before #register().", - featureName, featureAccessName)); + @ApiStatus.Experimental + protected final void internalOpen( + @NotNull Class viewClass, @NotNull Collection players, Object initialData) { + final V view = getRegisteredViewByType(viewClass); + final List viewers = players.stream() + .map(player -> view.getElementFactory().createViewer(player, null)) + .collect(Collectors.toList()); + + view.open(viewers, initialData); + } + + void addViewer(@NotNull Viewer viewer) { + synchronized (viewerById) { + viewerById.put(viewer.getId(), viewer); + } + } + + void removeViewer(@NotNull Viewer viewer) { + synchronized (viewerById) { + viewerById.remove(viewer.getId()); + } } } diff --git a/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/PaginationStateBuilder.java b/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/PaginationStateBuilder.java index 8c30648c..58064eb2 100644 --- a/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/PaginationStateBuilder.java +++ b/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/PaginationStateBuilder.java @@ -11,40 +11,24 @@ import me.devnatan.inventoryframework.state.State; import org.jetbrains.annotations.NotNull; +@SuppressWarnings("rawtypes") public final class PaginationStateBuilder< - C extends IFContext, SC extends IFSlotContext, B extends ItemComponentBuilder & ComponentFactory, V> { + Context extends IFContext, + SlotContext extends IFSlotContext, + Builder extends ItemComponentBuilder & ComponentFactory, + V> { - private final PlatformView root; + private final PlatformView root; private final Object sourceProvider; private char layoutTarget = LayoutSlot.FILLED_RESERVED_CHAR; - private PaginationElementFactory elementFactory; - private BiConsumer pageSwitchHandler; + private PaginationElementFactory elementFactory; + private BiConsumer pageSwitchHandler; - PaginationStateBuilder(PlatformView root, Object sourceProvider) { + PaginationStateBuilder(PlatformView root, Object sourceProvider) { this.root = root; this.sourceProvider = sourceProvider; } - PlatformView getRoot() { - return root; - } - - Object getSourceProvider() { - return sourceProvider; - } - - char getLayoutTarget() { - return layoutTarget; - } - - PaginationElementFactory getElementFactory() { - return elementFactory; - } - - BiConsumer getPageSwitchHandler() { - return pageSwitchHandler; - } - /** * Sets the element factory for pagination. *

@@ -57,7 +41,8 @@ BiConsumer getPageSwitchHandler() { * @param elementFactory The element factory. * @return This pagination state builder. */ - public PaginationStateBuilder elementFactory(@NotNull PaginationElementFactory elementFactory) { + public PaginationStateBuilder elementFactory( + @NotNull PaginationElementFactory elementFactory) { this.elementFactory = elementFactory; return this; } @@ -74,10 +59,11 @@ public PaginationStateBuilder elementFactory(@NotNull PaginationEle * @param itemFactory The item factory. * @return This pagination state builder. */ - public PaginationStateBuilder itemFactory(@NotNull BiConsumer itemFactory) { + public PaginationStateBuilder itemFactory( + @NotNull BiConsumer itemFactory) { return elementFactory((context, index, slot, value) -> { @SuppressWarnings("unchecked") - B builder = (B) root.getElementFactory().createComponentBuilder(context); + Builder builder = (Builder) root.getElementFactory().createComponentBuilder(context); builder.withSlot(slot).withExternallyManaged(true); itemFactory.accept(builder, value); return builder; @@ -96,7 +82,7 @@ public PaginationStateBuilder itemFactory(@NotNull BiConsumer * @param layoutTarget The target layout character. * @return This pagination state builder. */ - public PaginationStateBuilder layoutTarget(char layoutTarget) { + public PaginationStateBuilder layoutTarget(char layoutTarget) { this.layoutTarget = layoutTarget; return this; } @@ -110,7 +96,8 @@ public PaginationStateBuilder layoutTarget(char layoutTarget) { * @param pageSwitchHandler The page switch handler. * @return This pagination state builder. */ - public PaginationStateBuilder onPageSwitch(@NotNull BiConsumer pageSwitchHandler) { + public PaginationStateBuilder onPageSwitch( + @NotNull BiConsumer pageSwitchHandler) { this.pageSwitchHandler = pageSwitchHandler; return this; } @@ -129,4 +116,24 @@ public State build() { return root.buildPaginationState(this); } + + PlatformView getRoot() { + return root; + } + + Object getSourceProvider() { + return sourceProvider; + } + + char getLayoutTarget() { + return layoutTarget; + } + + PaginationElementFactory getElementFactory() { + return elementFactory; + } + + BiConsumer getPageSwitchHandler() { + return pageSwitchHandler; + } } diff --git a/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/PlatformView.java b/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/PlatformView.java index df725c1b..708a9255 100644 --- a/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/PlatformView.java +++ b/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/PlatformView.java @@ -1,11 +1,14 @@ package me.devnatan.inventoryframework; +import static java.lang.String.format; + +import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; -import java.util.stream.Collectors; import me.devnatan.inventoryframework.component.ComponentFactory; import me.devnatan.inventoryframework.component.ItemComponentBuilder; import me.devnatan.inventoryframework.component.Pagination; @@ -17,6 +20,7 @@ import me.devnatan.inventoryframework.context.IFRenderContext; import me.devnatan.inventoryframework.context.IFSlotClickContext; import me.devnatan.inventoryframework.context.IFSlotContext; +import me.devnatan.inventoryframework.context.PlatformContext; import me.devnatan.inventoryframework.internal.ElementFactory; import me.devnatan.inventoryframework.internal.PlatformUtils; import me.devnatan.inventoryframework.pipeline.AvailableSlotInterceptor; @@ -52,6 +56,7 @@ import org.jetbrains.annotations.NotNull; public abstract class PlatformView< + TFramework extends IFViewFrame, TItem extends ItemComponentBuilder & ComponentFactory, TContext extends IFContext, TOpenContext extends IFOpenContext, @@ -59,21 +64,213 @@ public abstract class PlatformView< TRenderContext extends IFRenderContext, TSlotContext extends IFSlotContext, TSlotClickContext extends IFSlotClickContext> - extends DefaultRootView { + extends DefaultRootView implements Iterable { - private IFViewFrame framework; + private TFramework framework; private boolean initialized; /** - * Creates a new configuration builder. + *

This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. + */ + @ApiStatus.Internal + public final TFramework getFramework() { + return framework; + } + + /** + * The initialization state of this view. * - * @return A new {@link ViewConfigBuilder} instance. + * @return If this view was initialized. */ - @NotNull - public final ViewConfigBuilder createConfig() { + final boolean isInitialized() { + return initialized; + } + + /** + * Sets the initialization state of this view. + * + * @param initialized The new initialization state. + */ + final void setInitialized(boolean initialized) { + this.initialized = initialized; + } + + /** + * Throws an exception if this view is already initialized. + * + * @throws IllegalStateException if this view is already initialized. + */ + private void requireNotInitialized() { + if (!isInitialized()) return; + throw new IllegalStateException( + "View is already initialized, please move this method call to class constructor or #onInit."); + } + + /** + * Closes all contexts that are currently active in this view. + */ + public final void closeForEveryone() { + getContexts().forEach(context -> ((PlatformContext) context).closeForEveryone()); + } + + /** + * Opens this view to one or more viewers. + *

+ * This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. + * + * @param viewers The viewers that'll see this view. + * @param initialData The initial data. + */ + @ApiStatus.Internal + public final void open(@NotNull List viewers, Object initialData) { + if (!isInitialized()) throw new IllegalStateException("Cannot open a uninitialized view"); + + final Viewer subject = viewers.size() == 1 ? viewers.get(0) : null; + final IFOpenContext context = getElementFactory().createOpenContext(this, subject, viewers, initialData); + + getPipeline().execute(StandardPipelinePhases.OPEN, context); + } + + /** + *

This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. + */ + @SuppressWarnings("rawtypes") + @ApiStatus.Internal + public final void navigateTo( + @NotNull Class target, @NotNull IFContext context, Object initialData) { + getFramework().getRegisteredViewByType(target).open(context.getViewers(), initialData); + } + + /** + *

This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. + */ + @SuppressWarnings("rawtypes") + @ApiStatus.Internal + public final void navigateTo( + @NotNull Class target, + @NotNull IFContext context, + @NotNull Viewer viewer, + Object initialData) { + getFramework().getRegisteredViewByType(target).open(Collections.singletonList(viewer), initialData); + } + + public final @NotNull ViewConfigBuilder createConfig() { return new ViewConfigBuilder().type(ViewType.CHEST); } + /** + * Returns the context that is linked to the specified viewer in this view. + *

+ * This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. + * + * @param viewer The viewer. + * @return The context of the viewer in this context. + * @throws IllegalArgumentException If there's no context linked to the given viewer. + */ + @ApiStatus.Internal + public final @NotNull IFContext getContext(@NotNull Viewer viewer) { + for (final IFContext context : getInternalContexts()) { + if (context.getIndexedViewers().containsKey(viewer.getId())) return context; + } + + throw new IllegalArgumentException(format("Unable to get context for %s", viewer)); + } + + /** + * Returns the context that is linked to the specified viewer in this view. + *

+ * This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. + * + * @param viewerId The id of the viewer. + * @return The context of the viewer in this context. + * @throws IllegalArgumentException If there's no context linked to the given viewer. + */ + public final @NotNull IFContext getContext(@NotNull String viewerId) { + for (final IFContext context : getInternalContexts()) { + if (context.getIndexedViewers().containsKey(viewerId)) return context; + } + + throw new IllegalArgumentException(format("Unable to get context for %s", viewerId)); + } + + /** + * Adds a context to this view. + *

+ * This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. + * + * @param context The context to add. + */ + @ApiStatus.Internal + public void addContext(@NotNull TContext context) { + synchronized (getInternalContexts()) { + getInternalContexts().add(context); + } + } + + /** + * Removes a given context from this view if that context is linked to this view. + *

+ * This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. + * + * @param context The context to remove. + */ + @ApiStatus.Internal + public void removeContext(@NotNull TContext context) { + synchronized (getInternalContexts()) { + getInternalContexts().removeIf(other -> other.getId() == context.getId()); + } + } + + /** + * Renders a given context in this view. + *

+ * This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. + * + * @param context The context to render. + */ + @ApiStatus.Internal + public void renderContext(@NotNull TContext context) { + getPipeline().execute(context); + + @SuppressWarnings("rawtypes") + final PlatformView view = (PlatformView) context.getRoot(); + context.getViewers().forEach(viewer -> { + view.getFramework().addViewer(viewer); + context.getContainer().open(viewer); + }); + } + + @ApiStatus.Internal + public void removeAndTryInvalidateContext(@NotNull Viewer viewer, @NotNull TContext context) { + context.removeViewer(viewer); + + @SuppressWarnings("rawtypes") + final PlatformView view = (PlatformView) context.getRoot(); + view.getFramework().removeViewer(viewer); + + final boolean canContextBeInvalidated = context.getViewers().isEmpty(); + if (canContextBeInvalidated) { + // TODO invalidate context + removeContext(context); + } + } + + @SuppressWarnings("unchecked") + @NotNull + @Override + public final Iterator iterator() { + return (Iterator) getContexts().iterator(); + } + /** * Creates an immutable state with an initial value. * @@ -382,11 +579,9 @@ protected final State asyncPaginationState( * @param The pagination data type. * @return A new pagination state builder. */ - @SuppressWarnings("unchecked") protected final PaginationStateBuilder buildPaginationState( @NotNull List sourceProvider) { - return new PaginationStateBuilder<>( - (PlatformView) this, sourceProvider); + return new PaginationStateBuilder<>(this, sourceProvider); } /** @@ -396,11 +591,9 @@ protected final PaginationStateBuilder The pagination data type. * @return A new pagination state builder. */ - @SuppressWarnings("unchecked") protected final PaginationStateBuilder buildPaginationState( @NotNull Supplier> sourceProvider) { - return new PaginationStateBuilder<>( - (PlatformView) this, sourceProvider); + return new PaginationStateBuilder<>(this, sourceProvider); } /** @@ -410,11 +603,9 @@ protected final PaginationStateBuilder The pagination data type. * @return A new pagination state builder. */ - @SuppressWarnings("unchecked") protected final PaginationStateBuilder buildPaginationState( @NotNull Function> sourceProvider) { - return new PaginationStateBuilder<>( - (PlatformView) this, sourceProvider); + return new PaginationStateBuilder<>(this, sourceProvider); } /** @@ -428,11 +619,9 @@ protected final PaginationStateBuilder PaginationStateBuilder buildAsyncPaginationState( @NotNull Function>> sourceProvider) { - return new PaginationStateBuilder<>( - (PlatformView) this, sourceProvider); + return new PaginationStateBuilder<>(this, sourceProvider); } final State buildPaginationState( @@ -529,30 +718,6 @@ public void onClose(TCloseContext close) {} @ApiStatus.OverrideOnly public void onClick(TSlotClickContext click) {} - /** - * Initialization state of this view. - * - * @return If this view was initialized. - */ - final boolean isInitialized() { - return initialized; - } - - /** - * Sets the initialization state of this view. - * - * @param initialized The new initialization state. - */ - final void setInitialized(boolean initialized) { - this.initialized = initialized; - } - - private void requireNotInitialized() { - if (!isInitialized()) return; - throw new IllegalStateException( - "View is already initialized, please move this method call to class construtor or #onInit."); - } - /** * Called internally before the first initialization. *

@@ -560,11 +725,12 @@ private void requireNotInitialized() { * * @throws IllegalStateException If this platform view is already initialized. */ - final void internalInitialization(IFViewFrame framework) { + @SuppressWarnings("unchecked") + final void internalInitialization(IFViewFrame framework) { if (isInitialized()) throw new IllegalStateException("Tried to call internal initialization but view is already initialized"); - this.framework = framework; + this.framework = (TFramework) framework; final Pipeline pipeline = getPipeline(); pipeline.intercept(StandardPipelinePhases.INIT, new PlatformInitInterceptor()); @@ -589,31 +755,4 @@ final void internalInitialization(IFViewFrame framework) { public @NotNull ElementFactory getElementFactory() { return PlatformUtils.getFactory(); } - - @Override - public final void open(@NotNull List viewers, Object initialData) { - if (!isInitialized()) throw new IllegalStateException("Cannot open a uninitialized view"); - - final Viewer subject = viewers.size() == 1 ? viewers.get(0) : null; - final IFOpenContext context = getElementFactory() - .createContext( - this, - null, - subject, - viewers.stream().collect(Collectors.toMap(Viewer::getId, Function.identity())), - IFOpenContext.class, - null, - initialData); - viewers.forEach(context::addViewer); - getPipeline().execute(StandardPipelinePhases.OPEN, context); - } - - /** - *

This is an internal inventory-framework API that should not be used from outside of - * this library. No compatibility guarantees are provided. - */ - @ApiStatus.Internal - public final IFViewFrame getFramework() { - return framework; - } } diff --git a/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/context/ConfinedContext.java b/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/context/ConfinedContext.java deleted file mode 100644 index 703d6dc7..00000000 --- a/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/context/ConfinedContext.java +++ /dev/null @@ -1,74 +0,0 @@ -package me.devnatan.inventoryframework.context; - -import java.util.Collections; -import java.util.Map; -import java.util.Objects; -import me.devnatan.inventoryframework.RootView; -import me.devnatan.inventoryframework.ViewContainer; -import me.devnatan.inventoryframework.Viewer; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -class ConfinedContext extends BaseViewContext implements IFConfinedContext { - - private final Viewer viewer; - - public ConfinedContext( - @NotNull RootView root, - @Nullable ViewContainer container, - Viewer subject, - @NotNull Map viewers, - Object initialData) { - super(root, container, viewers, initialData); - this.viewer = subject; - } - - @NotNull - @Override - public Viewer getViewer() { - return viewer; - } - - @Override - public void closeForPlayer() { - getContainer().close(viewer); - } - - @Override - public void openForPlayer(@NotNull Class other) { - openForPlayer(other, null); - } - - @Override - public void openForPlayer(@NotNull Class other, Object initialData) { - getRoot().getFramework().open(other, Collections.singleton(getViewer()), initialData); - } - - @Override - public void updateTitleForPlayer(@NotNull String title) { - getContainer().changeTitle(title, getViewer()); - } - - @Override - public void resetTitleForPlayer() { - getContainer().changeTitle(null, getViewer()); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ConfinedContext that = (ConfinedContext) o; - return Objects.equals(getViewer(), that.getViewer()); - } - - @Override - public int hashCode() { - return Objects.hash(getViewer()); - } - - @Override - public String toString() { - return "ConfinedContext{" + "viewer=" + viewer + "} " + super.toString(); - } -} diff --git a/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/context/PlatformConfinedContext.java b/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/context/PlatformConfinedContext.java new file mode 100644 index 00000000..28476049 --- /dev/null +++ b/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/context/PlatformConfinedContext.java @@ -0,0 +1,37 @@ +package me.devnatan.inventoryframework.context; + +import me.devnatan.inventoryframework.RootView; +import me.devnatan.inventoryframework.Viewer; +import org.jetbrains.annotations.NotNull; + +abstract class PlatformConfinedContext extends PlatformContext implements IFConfinedContext { + + @Override + public abstract Viewer getViewer(); + + @Override + public void closeForPlayer() { + getContainer().close(getViewer()); + } + + @Override + public void openForPlayer(@NotNull Class other) { + openForPlayer(other, null); + } + + @SuppressWarnings("unchecked") + @Override + public void openForPlayer(@NotNull Class other, Object initialData) { + getRoot().navigateTo(other, this, getViewer(), initialData); + } + + @Override + public void updateTitleForPlayer(@NotNull String title) { + getContainer().changeTitle(title, getViewer()); + } + + @Override + public void resetTitleForPlayer() { + getContainer().changeTitle(null, getViewer()); + } +} diff --git a/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/context/PlatformContext.java b/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/context/PlatformContext.java new file mode 100644 index 00000000..c83e3df5 --- /dev/null +++ b/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/context/PlatformContext.java @@ -0,0 +1,95 @@ +package me.devnatan.inventoryframework.context; + +import me.devnatan.inventoryframework.PlatformView; +import me.devnatan.inventoryframework.RootView; +import me.devnatan.inventoryframework.Viewer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public abstract class PlatformContext extends AbstractIFContext { + + PlatformContext() {} + + @SuppressWarnings("rawtypes") + @Override + public abstract @NotNull PlatformView getRoot(); + + /** + * The actual title of this context. + *

+ * If the title has been dynamically changed, it will return the {@link #getUpdatedTitle() updated title}. + * + * @return The updated title, the current title of this view, if null will return + * the default title for this view type. + */ + @NotNull + public String getTitle() { + return getUpdatedTitle() == null ? getInitialTitle() : getUpdatedTitle(); + } + + /** + * Title that has been {@link #updateTitleForEveryone(String) dynamically changed} in this context. + * + * @return The updated title or null if it wasn't updated. + * @see #updateTitleForEveryone(String) + */ + @Nullable + public final String getUpdatedTitle() { + return getContainer().getTitle(); + } + + /** + * Updates the container title for everyone that's viewing it. + * + *

This should not be used before the container is opened, if you need to set the __initial + * title__ use {@link IFOpenContext#modifyConfig()} on open handler instead. + * + *

This method is version dependant, so it may be that your server version is not yet + * supported, if you try to use this method and fail (can fail silently), report it to the + * library developers to add support to your version. + * + * @param title The new container title. + */ + public final void updateTitleForEveryone(@NotNull String title) { + for (final Viewer viewer : getViewers()) getContainer().changeTitle(title, viewer); + } + + /** + * Updates the container title to all viewers in this context, to the initially defined title. + * Must be used after {@link #updateTitleForEveryone(String)} to take effect. + */ + public final void resetTitleForEveryone() { + for (final Viewer viewer : getViewers()) getContainer().changeTitle(null, viewer); + } + + /** + * Closes this context's container to all viewers who are viewing it. + */ + public final void closeForEveryone() { + getContainer().close(); + } + + /** + * Opens a new view for all viewers in that context. + *

+ * This context will be immediately invalidated if there are no viewers left after opening. + * + * @param other The view to be opened. + */ + public final void openForEveryone(Class other) { + openForEveryone(other, null); + } + + /** + * Opens a new view for all viewers in that context with an initially defined data. + *

+ * This context will be immediately invalidated if there are no viewers left after opening. + * + * @param other The view to be opened. + * @param initialData The initial data. + */ + @SuppressWarnings("unchecked") + public final void openForEveryone(Class other, Object initialData) { + getRoot().navigateTo(other, this, initialData); + } +} diff --git a/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/context/PlatformRenderContext.java b/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/context/PlatformRenderContext.java index 19a56014..d931ad2b 100644 --- a/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/context/PlatformRenderContext.java +++ b/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/context/PlatformRenderContext.java @@ -3,10 +3,15 @@ import static java.lang.String.format; import static me.devnatan.inventoryframework.utils.SlotConverter.convertSlot; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; import java.util.function.BiConsumer; import java.util.function.BiFunction; import me.devnatan.inventoryframework.InventoryFrameworkException; +import me.devnatan.inventoryframework.PlatformView; import me.devnatan.inventoryframework.RootView; import me.devnatan.inventoryframework.ViewConfig; import me.devnatan.inventoryframework.ViewContainer; @@ -18,57 +23,50 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.UnmodifiableView; -abstract class PlatformRenderContext, C extends IFContext> extends ConfinedContext - implements IFRenderContext { +@SuppressWarnings("rawtypes") +public abstract class PlatformRenderContext, C extends IFContext> + extends PlatformConfinedContext implements IFRenderContext { + // --- Must inherit from parent context --- + private final UUID id; + protected final PlatformView root; + private final ViewConfig config; + private final Map viewers; + private final Object initialData; + private final Viewer subject; + + // --- Inherited --- + private final ViewContainer container; + + // --- Properties --- private final List componentBuilders = new ArrayList<>(); private final List layoutSlots = new ArrayList<>(); private BiFunction availableSlotFactory; - private final ViewConfig config; - private final UUID id; PlatformRenderContext( - UUID id, - RootView root, - ViewContainer container, + @NotNull UUID id, + @NotNull PlatformView root, + @NotNull ViewConfig config, + @NotNull ViewContainer container, + @NotNull Map viewers, Viewer subject, - Map viewers, - ViewConfig config, Object initialData) { - super(root, container, subject, viewers, initialData); this.id = id; + this.root = root; this.config = config; + this.container = container; + this.viewers = viewers; + this.subject = subject; + this.initialData = initialData; } - @Override - public @NotNull UUID getId() { - return id; - } - - @Override - public @NotNull ViewConfig getConfig() { - return config; - } - - @Override - public final @NotNull @UnmodifiableView List getComponentFactories() { - return Collections.unmodifiableList(componentBuilders); - } - - @Override - public final @NotNull List getLayoutSlots() { - return layoutSlots; - } - - @Override - public final void addLayoutSlot(@NotNull LayoutSlot layoutSlot) { - layoutSlots.add(layoutSlot); - } - - @Override - public BiFunction getAvailableSlotFactory() { - return availableSlotFactory; - } + /** + * Creates a new platform builder instance. + * + * @return A new platform builder instance. + */ + // TODO use ElementFactory's `createBuilder` instead + protected abstract T createBuilder(); /** * Creates a new item builder without a specified slot. @@ -215,14 +213,6 @@ public void layoutSlot(char character, @NotNull BiConsumer factory) })); } - /** - * Creates a new platform builder instance. - * - * @return A new platform builder instance. - */ - // TODO use ElementFactory's `createBuilder` instead - protected abstract T createBuilder(); - /** * Creates a new platform builder instance and registers it. * @@ -256,4 +246,84 @@ private void requireNonReservedLayoutCharacter(char character) { "The '%c' character cannot be used because it is only available for backwards compatibility. Please use another character.", character)); } + + @Override + public final @NotNull UUID getId() { + return id; + } + + @Override + public final @NotNull ViewContainer getContainer() { + return container; + } + + @Override + public final @NotNull ViewConfig getConfig() { + return config; + } + + @Override + public final @NotNull Map getIndexedViewers() { + return viewers; + } + + @Override + public final Object getInitialData() { + return initialData; + } + + @Override + public final Viewer getViewer() { + return subject; + } + + @Override + public final @NotNull @UnmodifiableView List getComponentFactories() { + return Collections.unmodifiableList(componentBuilders); + } + + @Override + public final @NotNull List getLayoutSlots() { + return layoutSlots; + } + + @Override + public final void addLayoutSlot(@NotNull LayoutSlot layoutSlot) { + layoutSlots.add(layoutSlot); + } + + @Override + public final BiFunction getAvailableSlotFactory() { + return availableSlotFactory; + } + + @Override + public final void closeForPlayer() { + tryThrowDoNotWorkWithSharedContext("closeForEveryone()"); + super.closeForPlayer(); + } + + @Override + public final void openForPlayer(@NotNull Class other) { + tryThrowDoNotWorkWithSharedContext("openForEveryone(Class)"); + super.openForPlayer(other); + } + + @Override + public final void openForPlayer(@NotNull Class other, Object initialData) { + tryThrowDoNotWorkWithSharedContext("openForEveryone(Class, Object)"); + super.openForPlayer(other, initialData); + } + + @Override + public final void updateTitleForPlayer(@NotNull String title) { + tryThrowDoNotWorkWithSharedContext("updateTitleForEveryone(String)"); + super.updateTitleForEveryone(title); + } + + @Override + public final void resetTitleForPlayer() { + tryThrowDoNotWorkWithSharedContext("resetTitleForEveryone()"); + super.resetTitleForPlayer(); + } } diff --git a/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/pipeline/PlatformCloseInterceptor.java b/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/pipeline/PlatformCloseInterceptor.java index ff605431..5170b627 100644 --- a/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/pipeline/PlatformCloseInterceptor.java +++ b/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/pipeline/PlatformCloseInterceptor.java @@ -4,18 +4,20 @@ import me.devnatan.inventoryframework.RootView; import me.devnatan.inventoryframework.VirtualView; import me.devnatan.inventoryframework.context.IFCloseContext; -import me.devnatan.inventoryframework.context.IFContext; +import me.devnatan.inventoryframework.context.PlatformRenderContext; import org.jetbrains.annotations.NotNull; +@SuppressWarnings("unchecked") public final class PlatformCloseInterceptor implements PipelineInterceptor { + @SuppressWarnings("rawtypes") @Override public void intercept(@NotNull PipelineContext pipeline, VirtualView subject) { if (!(subject instanceof IFCloseContext)) return; final IFCloseContext context = (IFCloseContext) subject; - final IFContext parent = context.getParent(); - final RootView root = context.getRoot(); + final PlatformRenderContext parent = (PlatformRenderContext) context.getParent(); + final PlatformView root = parent.getRoot(); tryCallPlatformRootCloseHandler(root, context); if (context.isCancelled()) { @@ -23,12 +25,7 @@ public void intercept(@NotNull PipelineContext pipeline, VirtualVie return; } - parent.removeViewer(context.getViewer()); - - if (canContextBeInvalidated(parent)) { - // TODO invalidate context - root.removeContext(parent); - } + root.removeAndTryInvalidateContext(context.getViewer(), context); } /** @@ -43,14 +40,4 @@ private void tryCallPlatformRootCloseHandler(RootView root, IFCloseContext conte ((PlatformView) root).onClose(context); } } - - /** - * Checks if a context can be invalidated by checking its viewers count. - * - * @param context The subject context. - * @return If context can be invalidated. - */ - private boolean canContextBeInvalidated(IFContext context) { - return context.getViewers().isEmpty(); - } } diff --git a/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/pipeline/PlatformOpenInterceptor.java b/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/pipeline/PlatformOpenInterceptor.java index 4f720b58..553dbeb9 100644 --- a/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/pipeline/PlatformOpenInterceptor.java +++ b/inventory-framework-platform/src/main/java/me/devnatan/inventoryframework/pipeline/PlatformOpenInterceptor.java @@ -1,5 +1,6 @@ package me.devnatan.inventoryframework.pipeline; +import java.util.HashMap; import me.devnatan.inventoryframework.*; import me.devnatan.inventoryframework.context.IFOpenContext; import me.devnatan.inventoryframework.context.IFRenderContext; @@ -39,6 +40,7 @@ public void intercept(@NotNull PipelineContext pipeline, VirtualVie }); } + @SuppressWarnings({"rawtypes", "unchecked"}) private void finishOpen(@NotNull PipelineContext pipeline, @NotNull IFOpenContext openContext) { if (openContext.isCancelled()) { pipeline.finish(); @@ -47,37 +49,40 @@ private void finishOpen(@NotNull PipelineContext pipeline, @NotNull if (skipOpen) return; - final RootView root = openContext.getRoot(); + final PlatformView root = (PlatformView) openContext.getRoot(); final IFRenderContext render = createRenderContext(openContext); - + root.addContext(render); root.renderContext(render); - render.getViewers().forEach(render.getContainer()::open); } IFRenderContext createRenderContext(IFOpenContext openContext) { - final ElementFactory elementFactory = openContext.getRoot().getElementFactory(); + final RootView root = openContext.getRoot(); final ViewConfig contextConfig = openContext.getConfig(); final String[] layout = contextConfig.getLayout(); if (layout != null) { if (contextConfig.getSize() != 0 && contextConfig.getSize() != layout.length) { + // TODO Needs a more detailed error message throw new InvalidLayoutException("The layout length differs from the set inventory size."); } openContext.modifyConfig().size(layout.length); } - final ViewContainer container = elementFactory.createContainer(openContext); - final IFRenderContext renderCtx = elementFactory.createContext( + final ElementFactory elementFactory = root.getElementFactory(); + final ViewContainer createdContainer = elementFactory.createContainer(openContext); + + final IFRenderContext context = elementFactory.createRenderContext( + openContext.getId(), openContext.getRoot(), - container, - openContext.getViewer(), - openContext.getIndexedViewers(), - IFRenderContext.class, - openContext, + openContext.getConfig(), + createdContainer, + new HashMap<>(), + openContext.getSubject(), openContext.getInitialData()); - openContext.getViewers().forEach(renderCtx::addViewer); - openContext.getRoot().addContext(renderCtx); - return renderCtx; + openContext.getIndexedViewers().values().forEach(viewer -> { + context.addViewer(viewer.withContext(context)); + }); + return context; } } diff --git a/inventory-framework-test/src/main/java/me/devnatan/inventoryframework/TestUtils.java b/inventory-framework-test/src/main/java/me/devnatan/inventoryframework/TestUtils.java index d9248844..b67e6b38 100644 --- a/inventory-framework-test/src/main/java/me/devnatan/inventoryframework/TestUtils.java +++ b/inventory-framework-test/src/main/java/me/devnatan/inventoryframework/TestUtils.java @@ -5,13 +5,11 @@ import java.util.HashMap; import me.devnatan.inventoryframework.context.IFContext; -import me.devnatan.inventoryframework.internal.MockElementFactory; public class TestUtils { public static RootView createRootMock() { RootView root = mock(RootView.class); - when(root.getElementFactory()).thenReturn(new MockElementFactory()); return root; } diff --git a/inventory-framework-test/src/main/java/me/devnatan/inventoryframework/internal/MockElementFactory.java b/inventory-framework-test/src/main/java/me/devnatan/inventoryframework/internal/MockElementFactory.java deleted file mode 100644 index ae0bf9ef..00000000 --- a/inventory-framework-test/src/main/java/me/devnatan/inventoryframework/internal/MockElementFactory.java +++ /dev/null @@ -1,108 +0,0 @@ -package me.devnatan.inventoryframework.internal; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import me.devnatan.inventoryframework.RootView; -import me.devnatan.inventoryframework.ViewContainer; -import me.devnatan.inventoryframework.Viewer; -import me.devnatan.inventoryframework.VirtualView; -import me.devnatan.inventoryframework.component.Component; -import me.devnatan.inventoryframework.component.ComponentBuilder; -import me.devnatan.inventoryframework.context.IFConfinedContext; -import me.devnatan.inventoryframework.context.IFContext; -import me.devnatan.inventoryframework.context.IFSlotContext; -import me.devnatan.inventoryframework.context.IFSlotRenderContext; -import me.devnatan.inventoryframework.logging.Logger; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public final class MockElementFactory extends ElementFactory { - @Override - public Logger getLogger() { - return mock(Logger.class); - } - - @Override - public @NotNull RootView createUninitializedRoot() { - return mock(RootView.class); - } - - @Override - public @NotNull ViewContainer createContainer(@NotNull IFContext context) { - return mock(ViewContainer.class); - } - - @Override - public @NotNull Viewer createViewer(Object... parameters) { - return mock(Viewer.class); - } - - @Override - public @NotNull String convertViewer(Object input) { - return (String) input; - } - - @Override - public @NotNull T createContext( - @NotNull RootView root, - ViewContainer container, - Viewer subject, - @NotNull Map viewers, - @NotNull Class kind, - @Nullable IFContext parent, - Object initialData) { - T value = mock(kind); - when(value.getContainer()).thenReturn(container); - if (value instanceof IFConfinedContext) - when(((IFConfinedContext) value).getViewer()).thenReturn(subject); - if (parent != null) when(value.getConfig()).thenCallRealMethod(); - return value; - } - - @Override - public @NotNull T createSlotContext( - int slot, - Component component, - @NotNull ViewContainer container, - Viewer subject, - @NotNull Map viewers, - @NotNull IFContext parent, - @NotNull Class kind) { - @SuppressWarnings("unchecked") - T value = (T) mock(kind); - when(value.getSlot()).thenReturn(slot); - when(value.getParent()).thenReturn(parent); - when(value.getContainer()).thenReturn(container); - - final List viewerList = new ArrayList<>(); - if (subject != null) viewerList.add(subject); - if (viewers != null) viewerList.addAll(viewers.values()); - - when(value.getViewers()).thenReturn(viewerList); - - if (kind.equals(IFSlotRenderContext.class)) { - IFSlotRenderContext slotRenderContext = (IFSlotRenderContext) value; - when(slotRenderContext.isCancelled()).thenReturn(false); - } - return value; - } - - @Override - public ComponentBuilder createComponentBuilder(@NotNull VirtualView root) { - return null; - } - - @Override - public boolean worksInCurrentPlatform() { - return true; - } - - @Override - public Job scheduleJobInterval(@NotNull RootView root, long intervalInTicks, @NotNull Runnable execution) { - return null; - } -}