Skip to content

Commit

Permalink
allow selecting multiple tabs in OwoItemGroup
Browse files Browse the repository at this point in the history
  • Loading branch information
gliscowo committed Jul 20, 2023
1 parent 8560ec4 commit 5317684
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 174 deletions.
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ minecraft_version=1.20.1
yarn_mappings=1.20.1+build.2
loader_version=0.14.21
# Mod Properties
mod_version=0.11.2-pre.1
mod_version=0.11.2-pre.5
maven_group=io.wispforest
archives_base_name=owo-lib
# Dependencies
fabric_version=0.83.1+1.20.1
fabric_version=0.85.0+1.20.1

# https://maven.shedaniel.me/me/shedaniel/RoughlyEnoughItems-fabric/
rei_version=12.0.625
Expand Down
75 changes: 11 additions & 64 deletions src/main/java/io/wispforest/owo/itemgroup/Icon.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,31 @@
import net.minecraft.item.ItemConvertible;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.ApiStatus;

/**
* An icon used for rendering on buttons in {@link OwoItemGroup}s
* <p>
* Default implementations provided for textures and itemstacks
* Default implementations provided for textures and item stacks
*/
@FunctionalInterface
public interface Icon {

void render(DrawContext context, int x, int y, int mouseX, int mouseY, float delta);

static Icon of(ItemStack stack) {
return new ItemIcon(stack);
return (context, x, y, mouseX, mouseY, delta) -> {
context.drawItemWithoutEntity(stack, x, y);
};
}

static Icon of(ItemConvertible item) {
return of(new ItemStack(item));
}

static Icon of(Identifier texture, int u, int v, int textureWidth, int textureHeight) {
return new TextureIcon(texture, u, v, textureWidth, textureHeight);
return (context, x, y, mouseX, mouseY, delta) -> {
context.drawTexture(texture, x, y, u, v, 16, 16, textureWidth, textureHeight);
};
}

/**
Expand All @@ -39,66 +43,9 @@ static Icon of(Identifier texture, int u, int v, int textureWidth, int textureHe
* @return The created icon instance
*/
static Icon of(Identifier texture, int textureSize, int frameDelay, boolean loop) {
return new AnimatedTextureIcon(texture, new SpriteSheetMetadata(textureSize, 16), frameDelay, loop);
}

/**
* Renders an {@link ItemStack}
*/
@ApiStatus.Internal
class ItemIcon implements Icon {

private final ItemStack stack;

private ItemIcon(ItemStack stack) {
this.stack = stack;
}

@Override
public void render(DrawContext context, int x, int y, int mouseX, int mouseY, float delta) {
context.drawItemWithoutEntity(stack, x, y);
}
}

/**
* Renders a 16x16 region of the given texture, starting at (u, v)
*/
@ApiStatus.Internal
class TextureIcon implements Icon {

private final Identifier texture;
private final int u, v;
private final int textureWidth, textureHeight;

public TextureIcon(Identifier texture, int u, int v, int textureWidth, int textureHeight) {
this.texture = texture;
this.u = u;
this.v = v;
this.textureWidth = textureWidth;
this.textureHeight = textureHeight;
}

@Override
public void render(DrawContext context, int x, int y, int mouseX, int mouseY, float delta) {
context.drawTexture(texture, x, y, u, v, 16, 16, textureWidth, textureHeight);
}
}

/**
* Similar to TextureIcon but allows 16x16 frame animated textures.
*/
@ApiStatus.Internal
class AnimatedTextureIcon implements Icon {
private final AnimatedTextureDrawable widget;

public AnimatedTextureIcon(Identifier texture, SpriteSheetMetadata spriteSheetMetadata, int frameDelay, boolean loop) {
this.widget = new AnimatedTextureDrawable(0, 0, 16, 16, texture, spriteSheetMetadata, frameDelay, loop);
}

@Override
public void render(DrawContext context, int x, int y, int mouseX, int mouseY, float delta) {
var widget = new AnimatedTextureDrawable(0, 0, 16, 16, texture, new SpriteSheetMetadata(textureSize, 16), frameDelay, loop);
return (context, x, y, mouseX, mouseY, delta) -> {
widget.render(x, y, context, mouseX, mouseY, delta);
}
};
}

}
150 changes: 129 additions & 21 deletions src/main/java/io/wispforest/owo/itemgroup/OwoItemGroup.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import io.wispforest.owo.itemgroup.gui.ItemGroupTab;
import io.wispforest.owo.mixin.itemgroup.ItemGroupAccessor;
import io.wispforest.owo.util.pond.OwoItemExtensions;
import it.unimi.dsi.fastutil.ints.IntArraySet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.ints.IntSets;
import net.fabricmc.api.EnvType;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.item.*;
Expand All @@ -23,15 +26,15 @@
import java.util.function.Supplier;

/**
* A custom implementation of {@link ItemGroup} that supports multiple sub-tabs
* within itself, as well as arbitrary buttons with defaults provided for links
* like GitHub, Modrinth, etc.
* Extensions for {@link ItemGroup} which support multiple sub-tabs
* within, as well as arbitrary buttons with defaults provided for links
* to places like GitHub, Modrinth, etc.
* <p>
* By default, Items are added via tags, however you can also use {@link OwoItemSettings}
* and set the tab for a given item via {@link OwoItemSettings#tab(int)}
* Tabs can be populated by using {@link OwoItemSettings} and setting the
* {@link OwoItemSettings#tab(int)}. Furthermore, tags can be used for easily populating
* tabs from data
* <p>
* Credits to Lemonszz for originally writing this for Biome Makeover.
* Adapted from Azagwens implementation
* This concept originated in Biome Makeover, where it was written by Lemonszz
*/
public abstract class OwoItemGroup extends ItemGroup {

Expand All @@ -42,38 +45,42 @@ public abstract class OwoItemGroup extends ItemGroup {
public final List<ItemGroupTab> tabs = new ArrayList<>();
public final List<ItemGroupButton> buttons = new ArrayList<>();

private final Identifier id;
private final Consumer<OwoItemGroup> initializer;

private final Supplier<Icon> iconSupplier;
private Icon icon;

private int selectedTab = 0;
private final IntSet activeTabs = new IntArraySet();
private final IntSet activeTabsView = IntSets.unmodifiable(this.activeTabs);
private boolean initialized = false;

private final int tabStackHeight;
private final int buttonStackHeight;
private final Identifier customTexture;
private final boolean useDynamicTitle;
private final boolean displaySingleTab;
private final boolean allowMultiSelect;

protected OwoItemGroup(Identifier id, Consumer<OwoItemGroup> initializer, Supplier<Icon> iconSupplier, int tabStackHeight, int buttonStackHeight, @Nullable Identifier customTexture, boolean useDynamicTitle, boolean displaySingleTab) {
protected OwoItemGroup(Identifier id, Consumer<OwoItemGroup> initializer, Supplier<Icon> iconSupplier, int tabStackHeight, int buttonStackHeight, @Nullable Identifier customTexture, boolean useDynamicTitle, boolean displaySingleTab, boolean allowMultiSelect) {
super(null, -1, Type.CATEGORY, Text.translatable("itemGroup.%s.%s".formatted(id.getNamespace(), id.getPath())), () -> ItemStack.EMPTY, (displayContext, entries) -> {});
this.id = id;
this.initializer = initializer;
this.iconSupplier = iconSupplier;
this.tabStackHeight = tabStackHeight;
this.buttonStackHeight = buttonStackHeight;
this.customTexture = customTexture;
this.useDynamicTitle = useDynamicTitle;
this.displaySingleTab = displaySingleTab;
this.allowMultiSelect = allowMultiSelect;

((ItemGroupAccessor) this).owo$setEntryCollector((context, entries) -> {
if (!this.initialized) {
throw new IllegalStateException("oωo item group not initialized, was 'initialize()' called?");
}
this.getSelectedTab().contentSupplier().addItems(context, entries);
this.collectItemsFromRegistry(entries, true);

this.activeTabs.forEach(tabIdx -> {
this.tabs.get(tabIdx).contentSupplier().addItems(context, entries);
this.collectItemsFromRegistry(entries, tabIdx);
});
});
}

Expand All @@ -95,6 +102,15 @@ public void initialize() {
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) this.initializer.accept(this);
if (tabs.size() == 0) this.tabs.add(PLACEHOLDER_TAB);

if (this.allowMultiSelect) {
for (int tabIdx = 0; tabIdx < this.tabs.size(); tabIdx++) {
if (!this.tabs.get(tabIdx).primary()) continue;
this.activeTabs.add(tabIdx);
}
} else {
this.activeTabs.add(0);
}

this.initialized = true;
}

Expand Down Expand Up @@ -179,31 +195,117 @@ public void updateEntries(DisplayContext context) {

var searchEntries = new SearchOnlyEntries(this, context.enabledFeatures());

this.collectItemsFromRegistry(searchEntries, false);
this.collectItemsFromRegistry(searchEntries, -1);
this.tabs.forEach(tab -> tab.contentSupplier().addItems(context, searchEntries));

((ItemGroupAccessor) this).owo$setSearchTabStacks(searchEntries.searchTabStacks);
}

protected void collectItemsFromRegistry(Entries entries, boolean matchTab) {
protected void collectItemsFromRegistry(Entries entries, int tab) {
Registries.ITEM.stream()
.filter(item -> ((OwoItemExtensions) item).owo$group() == this && (!matchTab || ((OwoItemExtensions) item).owo$tab() == this.selectedTab))
.filter(item -> ((OwoItemExtensions) item).owo$group() == this && (tab < 0 || tab == ((OwoItemExtensions) item).owo$tab()))
.forEach(item -> ((OwoItemExtensions) item).owo$stackGenerator().accept(item, entries));
}

// Getters and setters

/**
* @deprecated Use {@link #selectSingleTab(int, DisplayContext)} instead
*/
@Deprecated(forRemoval = true)
public void setSelectedTab(int selectedTab, DisplayContext context) {
this.selectedTab = selectedTab;
this.updateEntries(context);
this.selectSingleTab(selectedTab, context);
}

/**
* @deprecated On an item group which allows multiple selection, this
* returns the first selected tab only. Instead, call {@link #selectedTabs()}
*/
@Deprecated(forRemoval = true)
public ItemGroupTab getSelectedTab() {
return tabs.get(selectedTab);
return this.tabs.get(this.getSelectedTabIndex());
}

/**
* @deprecated On an item group which allows multiple selection, this
* returns the index of the first selected tab only. Instead, call {@link #selectedTabs()}
*/
@Deprecated(forRemoval = true)
public int getSelectedTabIndex() {
return selectedTab;
return this.activeTabs.iterator().nextInt();
}

/**
* Select only {@code tab}, deselecting all other tabs,
* using {@code context} for re-population
*/
public void selectSingleTab(int tab, DisplayContext context) {
this.activeTabs.clear();
this.activeTabs.add(tab);

this.updateEntries(context);
}

/**
* Select {@code tab} in addition to other currently selected
* tabs, using {@code context} for re-population.
* <p>
* If this group does not allow multiple selection, behaves
* like {@link #selectSingleTab(int, DisplayContext)}
*/
public void selectTab(int tab, DisplayContext context) {
if (!this.allowMultiSelect) {
this.activeTabs.clear();
}

this.activeTabs.add(tab);
this.updateEntries(context);
}

/**
* Deselect {@code tab} if it is currently selected, using {@code context} for
* re-population. If this results in no tabs being selected, all tabs are
* automatically selected instead
*/
public void deselectTab(int tab, DisplayContext context) {
if (!this.allowMultiSelect) return;

this.activeTabs.remove(tab);
if (this.activeTabs.isEmpty()) {
for (int tabIdx = 0; tabIdx < this.tabs.size(); tabIdx++) {
this.activeTabs.add(tabIdx);
}
}

this.updateEntries(context);
}

/**
* Shorthand for {@link #selectTab(int, DisplayContext)} or
* {@link #deselectTab(int, DisplayContext)}, depending on the tabs
* current state
*/
public void toggleTab(int tab, DisplayContext context) {
if (this.isTabSelected(tab)) {
this.deselectTab(tab, context);
} else {
this.selectTab(tab, context);
}
}

/**
* @return A set containing the indices of all currently
* selected tabs
*/
public IntSet selectedTabs() {
return this.activeTabsView;
}

/**
* @return {@code true} if {@code tab} is currently selected
*/
public boolean isTabSelected(int tab) {
return this.activeTabs.contains(tab);
}

public Identifier getCustomTexture() {
Expand Down Expand Up @@ -260,6 +362,7 @@ public static class Builder {
private @Nullable Identifier customTexture = null;
private boolean useDynamicTitle = true;
private boolean displaySingleTab = false;
private boolean allowMultiSelect = true;

private Builder(Identifier id, Supplier<Icon> iconSupplier) {
this.id = id;
Expand Down Expand Up @@ -296,8 +399,13 @@ public Builder displaySingleTab() {
return this;
}

public Builder withoutMultipleSelection() {
this.allowMultiSelect = false;
return this;
}

public OwoItemGroup build() {
final var group = new OwoItemGroup(id, initializer, iconSupplier, tabStackHeight, buttonStackHeight, customTexture, useDynamicTitle, displaySingleTab) {};
final var group = new OwoItemGroup(id, initializer, iconSupplier, tabStackHeight, buttonStackHeight, customTexture, useDynamicTitle, displaySingleTab, allowMultiSelect) {};
Registry.register(Registries.ITEM_GROUP, this.id, group);
return group;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,16 @@ public Identifier texture() {

@Override
public Icon icon() {
return icon;
return this.icon;
}

@Override
public Text tooltip() {
return tooltip;
return this.tooltip;
}

public Runnable action() {
return action;
return this.action;
}

}
Loading

0 comments on commit 5317684

Please sign in to comment.