Skip to content

Commit 6e6304d

Browse files
Reduce coupling between gui / window / item implementations
This commit reduces coupling between gui / window / item implementations by moving observability functionality and things like Gui#handleClick to the api. In turn, AbstractGui and AbstractWindow can be package private and Item / BoundItem can be unsealed.
1 parent 6d391cb commit 6e6304d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+507
-457
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package xyz.xenondevs.invui;
2+
3+
/**
4+
* Something that can be observed by a {@link Observer}.
5+
*/
6+
public interface Observable {
7+
8+
/**
9+
* Adds an {@link Observer} to this {@link Observable}.
10+
* @param who The {@link Observer} to that observes this {@link Observable}.
11+
* @param what An integer specifying what part of this {@link Observable} the {@link Observer} is observing.
12+
* @param how An integer specifying how the {@link Observer} is observing this {@link Observable}.
13+
* Used to {@link Observer#notifyUpdate(int) notify} the {@link Observer} about updates.
14+
*/
15+
void addObserver(Observer who, int what, int how);
16+
17+
/**
18+
* Removes an {@link Observer} from this {@link Observable}.
19+
* @param who The {@link Observer} that is no longer observes this {@link Observable}.
20+
* @param what An integer specifying what part of this {@link Observable} the {@link Observer} was observing.
21+
* @param how An integer specifying how the {@link Observer} was observing this {@link Observable}.
22+
*/
23+
void removeObserver(Observer who, int what, int how);
24+
25+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package xyz.xenondevs.invui;
2+
3+
/**
4+
* Something that can observe an {@link Observable}.
5+
*/
6+
public interface Observer {
7+
8+
/**
9+
* Notifies this {@link Observer} about an update of a {@link Observable} that this
10+
* {@link Observer} was registered to using the given number as {@code how}.
11+
*
12+
* @param i The {@code how} integer that was used to register this {@link Observer} to the {@link Observable}.
13+
*/
14+
void notifyUpdate(int i);
15+
16+
}

invui/src/main/java/xyz/xenondevs/invui/gui/AbstractGui.java

Lines changed: 72 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
import org.bukkit.entity.Player;
66
import org.bukkit.inventory.ItemStack;
77
import org.bukkit.inventory.PlayerInventory;
8-
import org.jetbrains.annotations.ApiStatus.Internal;
98
import org.jetbrains.annotations.Unmodifiable;
109
import org.jspecify.annotations.Nullable;
1110
import xyz.xenondevs.invui.Click;
12-
import xyz.xenondevs.invui.internal.ViewerAtSlot;
11+
import xyz.xenondevs.invui.Observer;
1312
import xyz.xenondevs.invui.internal.util.*;
13+
import xyz.xenondevs.invui.inventory.CompositeInventory;
1414
import xyz.xenondevs.invui.inventory.Inventory;
1515
import xyz.xenondevs.invui.inventory.ObscuredInventory;
1616
import xyz.xenondevs.invui.inventory.OperationCategory;
@@ -22,7 +22,7 @@
2222
import xyz.xenondevs.invui.item.ItemProvider;
2323
import xyz.xenondevs.invui.state.MutableProperty;
2424
import xyz.xenondevs.invui.util.ItemUtils;
25-
import xyz.xenondevs.invui.window.AbstractWindow;
25+
import xyz.xenondevs.invui.util.ObserverAtSlot;
2626
import xyz.xenondevs.invui.window.Window;
2727
import xyz.xenondevs.invui.window.WindowManager;
2828

@@ -31,14 +31,7 @@
3131
import java.util.function.Supplier;
3232
import java.util.stream.Collectors;
3333

34-
/**
35-
* @hidden
36-
*/
37-
@Internal
38-
public sealed abstract class AbstractGui
39-
implements Gui
40-
permits NormalGuiImpl, AbstractPagedGui, AbstractScrollGui, TabGuiImpl
41-
{
34+
non-sealed abstract class AbstractGui implements Gui {
4235

4336
public static final boolean DEFAULT_FROZEN = false;
4437
public static final boolean DEFAULT_IGNORE_OBSCURED_INVENTORY_SLOTS = true;
@@ -48,7 +41,7 @@ public sealed abstract class AbstractGui
4841
private final int height;
4942
private final int size;
5043
private final @Nullable SlotElement[] slotElements;
51-
private final @Nullable Set<ViewerAtSlot>[] viewers;
44+
private final @Nullable Set<ObserverAtSlot>[] observers;
5245

5346
private final MutableProperty<Boolean> frozen;
5447
private final MutableProperty<Boolean> ignoreObscuredInventorySlots;
@@ -84,33 +77,35 @@ public sealed abstract class AbstractGui
8477
this.background = background;
8578

8679
slotElements = new SlotElement[size];
87-
viewers = new Set[size];
80+
observers = new Set[size];
8881

8982
this.background.observeWeak(this, AbstractGui::notifyWindowsOnBackgroundSlots);
9083
}
9184

85+
@Override
9286
public void handleClick(int slot, Click click) {
9387
// ignore all clicks if the gui is frozen
9488
if (isFrozen())
9589
return;
9690

9791
SlotElement slotElement = slotElements[slot];
9892
switch (slotElement) {
99-
case SlotElement.GuiLink le -> ((AbstractGui) le.gui()).handleClick(le.slot(), click);
93+
case SlotElement.GuiLink le -> le.gui().handleClick(le.slot(), click);
10094
case SlotElement.Item ie -> ie.item().handleClick(click.clickType(), click.player(), click);
10195
case SlotElement.InventoryLink ie -> handleInvSlotElementClick(ie, click);
10296
case null -> {}
10397
}
10498
}
10599

106-
public void handleBundleSelect(Player player, int slot, int bundleSlot) {
100+
@Override
101+
public void handleBundleSelect(int slot, Player player, int bundleSlot) {
107102
// ignore all clicks if the gui is frozen
108103
if (isFrozen())
109104
return;
110105

111106
SlotElement slotElement = slotElements[slot];
112107
switch (slotElement) {
113-
case SlotElement.GuiLink le -> ((AbstractGui) le.gui()).handleBundleSelect(player, le.slot(), bundleSlot);
108+
case SlotElement.GuiLink le -> le.gui().handleBundleSelect(le.slot(), player, bundleSlot);
114109
case SlotElement.Item ie -> ie.item().handleBundleSelect(player, bundleSlot);
115110
case SlotElement.InventoryLink ie -> handleInvBundleSelect(player, ie.inventory(), ie.slot(), bundleSlot);
116111
case null -> {}
@@ -284,18 +279,14 @@ private void handleInvNumberKey(Click click, Inventory inventory, int slot) {
284279
Player player = click.player();
285280
ItemStack clicked = inventory.getItem(slot);
286281

287-
AbstractWindow<?> window = (AbstractWindow<?>) WindowManager.getInstance().getOpenWindow(player);
282+
Window window = WindowManager.getInstance().getOpenWindow(player);
288283
assert window != null;
289284

290285
SlotElement.GuiLink link = window.getGuiAtHotbar(click.hotbarButton());
291286
if (link == null)
292287
return;
293-
SlotElement hotbarElement = link.gui().getSlotElement(link.slot());
294-
if (hotbarElement == null)
295-
return;
296-
hotbarElement = hotbarElement.getHoldingElement();
297288

298-
if (hotbarElement instanceof SlotElement.InventoryLink(var otherInventory, var otherSlot, var unused)) {
289+
if (link.getHoldingElement() instanceof SlotElement.InventoryLink(var otherInventory, var otherSlot, var unused)) {
299290
if (inventory == otherInventory && slot == otherSlot)
300291
return;
301292

@@ -351,10 +342,27 @@ private void handleInvDoubleClick(Click click) {
351342
if (ItemUtils.isEmpty(player.getItemOnCursor()))
352343
return;
353344

354-
// windows handle cursor collect because it is a cross-inventory / cross-gui operation
345+
// requires window as collect to cursor is a cross-gui operation
355346
Window window = WindowManager.getInstance().getOpenWindow(player);
356347
assert window != null;
357-
((AbstractWindow<?>) window).handleCursorCollect(click);
348+
349+
// the template item stack that is used to collect similar items
350+
ItemStack template = player.getItemOnCursor();
351+
352+
// create a composite inventory consisting of all the gui's inventories and the player's inventory
353+
List<? extends Inventory> inventories = window.getGuis().stream()
354+
.flatMap(g -> g.getInventories().stream())
355+
.sorted(Comparator.<Inventory>comparingInt(inv -> inv.getGuiPriority(OperationCategory.COLLECT)).reversed())
356+
.toList();
357+
Inventory inventory = new CompositeInventory(inventories);
358+
359+
// collect items from inventories until the cursor is full
360+
UpdateReason updateReason = new PlayerUpdateReason.Click(player, click);
361+
int amount = inventory.collectSimilar(updateReason, template);
362+
363+
// put collected items on cursor
364+
template.setAmount(amount);
365+
player.setItemOnCursor(template);
358366
}
359367

360368
private void handleInvMiddleClick(Click click, Inventory inventory, int slot) {
@@ -476,10 +484,10 @@ private Map<? extends Inventory, Set<Integer>> getAllActiveInventorySlots(Invent
476484

477485
@Override
478486
public void notifyWindows() {
479-
synchronized (viewers) {
480-
for (var viewerSet : viewers) {
481-
if (viewerSet != null) {
482-
for (var viewerAtSlot : viewerSet) {
487+
synchronized (observers) {
488+
for (var observerSet : observers) {
489+
if (observerSet != null) {
490+
for (var viewerAtSlot : observerSet) {
483491
viewerAtSlot.notifyUpdate();
484492
}
485493
}
@@ -493,65 +501,69 @@ public void notifyWindows(int index) {
493501
if (element == null)
494502
return;
495503

496-
synchronized (viewers) {
497-
var viewerSet = viewers[index];
498-
if (viewerSet == null)
504+
synchronized (observers) {
505+
var observerSet = observers[index];
506+
if (observerSet == null)
499507
return;
500508

501-
for (var viewerAtSlot : viewerSet) {
509+
for (var viewerAtSlot : observerSet) {
502510
viewerAtSlot.notifyUpdate();
503511
}
504512
}
505513
}
506514

507515
private void notifyWindowsOnBackgroundSlots() {
508-
synchronized (viewers) {
516+
synchronized (observers) {
509517
for (int i = 0; i < getSize(); i++) {
510518
if (slotElements[i] != null)
511519
continue;
512520

513-
var viewerSet = viewers[i];
514-
if (viewerSet == null)
521+
var observerSet = observers[i];
522+
if (observerSet == null)
515523
continue;
516524

517-
for (var viewerAtSlot : viewerSet) {
525+
for (var viewerAtSlot : observerSet) {
518526
viewerAtSlot.notifyUpdate();
519527
}
520528
}
521529
}
522530
}
523531

524-
public void addViewer(AbstractWindow<?> who, int what, int how) {
525-
synchronized (viewers) {
526-
var viewerSet = this.viewers[what];
527-
if (viewerSet == null) {
528-
viewerSet = new HashSet<>();
529-
this.viewers[what] = viewerSet;
532+
@Override
533+
public void addObserver(Observer who, int what, int how) {
534+
synchronized (observers) {
535+
var observerSet = this.observers[what];
536+
if (observerSet == null) {
537+
observerSet = new HashSet<>();
538+
this.observers[what] = observerSet;
530539
}
531-
viewerSet.add(new ViewerAtSlot(who, how));
540+
observerSet.add(new ObserverAtSlot(who, how));
532541
}
533542
}
534543

535-
public void removeViewer(AbstractWindow<?> who, int what, int how) {
536-
synchronized (viewers) {
537-
var viewerSet = this.viewers[what];
538-
if (viewerSet != null) {
539-
viewerSet.remove(new ViewerAtSlot(who, how));
540-
if (viewerSet.isEmpty())
541-
this.viewers[what] = null;
544+
@Override
545+
public void removeObserver(Observer who, int what, int how) {
546+
synchronized (observers) {
547+
var observerSet = this.observers[what];
548+
if (observerSet != null) {
549+
observerSet.remove(new ObserverAtSlot(who, how));
550+
if (observerSet.isEmpty())
551+
this.observers[what] = null;
542552
}
543553
}
544554
}
545555

546556
@Override
547557
public @Unmodifiable Collection<Window> getWindows() {
548-
synchronized (viewers) {
558+
synchronized (observers) {
549559
var windows = new HashSet<Window>();
550-
for (var viewerSet : viewers) {
551-
if (viewerSet != null) {
552-
for (var viewerAtSlot : viewerSet) {
553-
windows.add(viewerAtSlot.window());
554-
}
560+
for (var observerSet : observers) {
561+
if (observerSet == null)
562+
continue;
563+
for (var viewerAtSlot : observerSet) {
564+
if (!(viewerAtSlot.observer() instanceof Window w))
565+
continue;
566+
windows.add(w);
555567
}
556568
}
557569

@@ -670,7 +682,7 @@ public void setSlotElement(int index, @Nullable SlotElement slotElement) {
670682
}
671683

672684
// notify parents that a slot element has been changed
673-
var viewers = this.viewers[index];
685+
var viewers = this.observers[index];
674686
if (viewers != null) {
675687
for (var viewer : viewers) {
676688
viewer.notifyUpdate();
@@ -731,10 +743,9 @@ public void addItems(Item... items) {
731743

732744
if (slotElement instanceof SlotElement.Item) {
733745
return ((SlotElement.Item) slotElement).item();
734-
} else if (slotElement instanceof SlotElement.GuiLink) {
735-
SlotElement holdingElement = slotElement.getHoldingElement();
736-
if (holdingElement instanceof SlotElement.Item)
737-
return ((SlotElement.Item) holdingElement).item();
746+
} else if (slotElement instanceof SlotElement.GuiLink l) {
747+
if (l.getHoldingElement() instanceof SlotElement.Item(Item item))
748+
return item;
738749
}
739750

740751
return null;

invui/src/main/java/xyz/xenondevs/invui/gui/AbstractPagedGui.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
import org.jetbrains.annotations.UnmodifiableView;
55
import org.jspecify.annotations.Nullable;
66
import xyz.xenondevs.invui.internal.util.CollectionUtils;
7-
import xyz.xenondevs.invui.internal.util.SlotUtils;
87
import xyz.xenondevs.invui.internal.util.FuncUtils;
8+
import xyz.xenondevs.invui.internal.util.SlotUtils;
99
import xyz.xenondevs.invui.item.ItemProvider;
1010
import xyz.xenondevs.invui.state.MutableProperty;
1111

@@ -15,11 +15,7 @@
1515
import java.util.SequencedSet;
1616
import java.util.function.BiConsumer;
1717

18-
sealed abstract class AbstractPagedGui<C>
19-
extends AbstractGui
20-
implements PagedGui<C>
21-
permits PagedInventoriesGuiImpl, PagedItemsGuiImpl, PagedNestedGuiImpl
22-
{
18+
non-sealed abstract class AbstractPagedGui<C> extends AbstractGui implements PagedGui<C> {
2319

2420
private static final int DEFAULT_PAGE = 0;
2521

invui/src/main/java/xyz/xenondevs/invui/gui/AbstractScrollGui.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
import org.jspecify.annotations.Nullable;
66
import xyz.xenondevs.invui.internal.util.ArrayUtils;
77
import xyz.xenondevs.invui.internal.util.CollectionUtils;
8-
import xyz.xenondevs.invui.internal.util.SlotUtils;
98
import xyz.xenondevs.invui.internal.util.FuncUtils;
9+
import xyz.xenondevs.invui.internal.util.SlotUtils;
1010
import xyz.xenondevs.invui.item.ItemProvider;
1111
import xyz.xenondevs.invui.state.MutableProperty;
1212

@@ -16,11 +16,7 @@
1616
import java.util.SequencedSet;
1717
import java.util.function.BiConsumer;
1818

19-
sealed abstract class AbstractScrollGui<C>
20-
extends AbstractGui
21-
implements ScrollGui<C>
22-
permits ScrollItemsGuiImpl, ScrollNestedGuiImpl, ScrollInventoryGuiImpl
23-
{
19+
non-sealed abstract class AbstractScrollGui<C> extends AbstractGui implements ScrollGui<C> {
2420

2521
private static final int DEFAULT_LINE = 0;
2622

0 commit comments

Comments
 (0)