aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJuuxel <6596629+Juuxel@users.noreply.github.com>2020-09-24 12:02:38 +0300
committerGitHub <noreply@github.com>2020-09-24 12:02:38 +0300
commit016eb1efda675e676e5cde655ba382a77b1552b5 (patch)
tree453a69a08aabd3b234e7179a7ce6884d95781768 /src
parent283db1edc77209344888210c4b618595874707fe (diff)
downloadLibGui-016eb1efda675e676e5cde655ba382a77b1552b5.tar.gz
LibGui-016eb1efda675e676e5cde655ba382a77b1552b5.tar.bz2
LibGui-016eb1efda675e676e5cde655ba382a77b1552b5.zip
Tabs, card panels and showing/hiding widgets (#74)3.0.0
* Add beta API for hiding and showing widget peers, add default implementation for slots * Add WCardPanel * Tab thingies * Improve WPanel.toString * Fix tabs, add dark mode * Add box fillers * Tabs again * Tab go brrr * Revert modmenu changes * Fix card panels not initialising hidden widgets properly * Fix slots not being hidden when they should be * Things * Revert "Add box fillers" This reverts commit 1ea1bfbb * foo * revert more modmenu changes * Add tab titles and switch to a builder model for adding tabs * Document tab builders * Make hidden widgets release their focus * Replace outdated since tags with TAB_VERSION * Fix compilation of WTabPanel * TAB_VERSION => 3.0.0 * Add focusing support to tabs
Diffstat (limited to 'src')
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/ValidatedSlot.java39
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/client/CottonClientScreen.java2
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/client/CottonInventoryScreen.java2
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/client/modmenu/ConfigGui.java2
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/client/modmenu/ModMenuSupport.java5
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/impl/access/SlotAccessor.java14
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WCardPanel.java189
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WItemSlot.java27
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WPanel.java33
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WTabPanel.java370
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WWidget.java43
-rw-r--r--src/main/resources/assets/libgui/textures/widget/tab/focus.pngbin0 -> 633 bytes
-rw-r--r--src/main/resources/assets/libgui/textures/widget/tab/selected_dark.pngbin0 -> 629 bytes
-rw-r--r--src/main/resources/assets/libgui/textures/widget/tab/selected_light.pngbin0 -> 625 bytes
-rw-r--r--src/main/resources/assets/libgui/textures/widget/tab/unselected_dark.pngbin0 -> 628 bytes
-rw-r--r--src/main/resources/assets/libgui/textures/widget/tab/unselected_light.pngbin0 -> 625 bytes
-rw-r--r--src/main/resources/fabric.mod.json3
-rw-r--r--src/main/resources/mixins.libgui.accessors.json14
18 files changed, 732 insertions, 11 deletions
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/ValidatedSlot.java b/src/main/java/io/github/cottonmc/cotton/gui/ValidatedSlot.java
index a45fee6..69acc85 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/ValidatedSlot.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/ValidatedSlot.java
@@ -3,6 +3,7 @@ package io.github.cottonmc.cotton.gui;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import io.github.cottonmc.cotton.gui.widget.WItemSlot;
+import io.github.cottonmc.cotton.gui.impl.access.SlotAccessor;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.ItemStack;
@@ -16,15 +17,20 @@ import java.util.function.Predicate;
public class ValidatedSlot extends Slot {
private static final Logger LOGGER = LogManager.getLogger();
private final int slotNumber;
+ // Original positions that will be restored when this slot is reshown
+ private final int originalX, originalY;
private boolean insertingAllowed = true;
private boolean takingAllowed = true;
private Predicate<ItemStack> filter;
protected final Multimap<WItemSlot, WItemSlot.ChangeListener> listeners = HashMultimap.create();
+ private boolean visible = true;
public ValidatedSlot(Inventory inventory, int index, int x, int y) {
super(inventory, index, x, y);
if (inventory==null) throw new IllegalArgumentException("Can't make an itemslot from a null inventory!");
this.slotNumber = index;
+ this.originalX = x;
+ this.originalY = y;
}
@Override
@@ -142,4 +148,35 @@ public class ValidatedSlot extends Slot {
Objects.requireNonNull(listener, "listener");
listeners.put(owner, listener);
}
-} \ No newline at end of file
+
+ /**
+ * Tests whether this slot is visible.
+ *
+ * @return true if this slot is visible, false otherwise
+ * @since 3.0.0
+ */
+ public boolean isVisible() {
+ return visible;
+ }
+
+ /**
+ * Sets whether this slot is visible.
+ *
+ * @param visible true if this slot if visible, false otherwise
+ * @since 3.0.0
+ */
+ public void setVisible(boolean visible) {
+ if (this.visible != visible) {
+ this.visible = visible;
+
+ SlotAccessor accessor = (SlotAccessor) this;
+ if (visible) {
+ accessor.setX(originalX);
+ accessor.setY(originalY);
+ } else {
+ accessor.setX(-100000);
+ accessor.setY(-100000);
+ }
+ }
+ }
+}
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/client/CottonClientScreen.java b/src/main/java/io/github/cottonmc/cotton/gui/client/CottonClientScreen.java
index 01b0c70..8fc53e1 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/client/CottonClientScreen.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/client/CottonClientScreen.java
@@ -52,6 +52,8 @@ public class CottonClientScreen extends Screen implements TextHoverRendererScree
super.init(client, screenWidth, screenHeight);
client.keyboard.setRepeatEvents(true);
+ WPanel root = description.getRootPanel();
+ if (root != null) root.addPainters();
description.addPainters();
reposition(screenWidth, screenHeight);
}
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/client/CottonInventoryScreen.java b/src/main/java/io/github/cottonmc/cotton/gui/client/CottonInventoryScreen.java
index 210470d..e3a61a8 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/client/CottonInventoryScreen.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/client/CottonInventoryScreen.java
@@ -65,6 +65,8 @@ public class CottonInventoryScreen<T extends SyncedGuiDescription> extends Handl
super.init(client, screenWidth, screenHeight);
client.keyboard.setRepeatEvents(true);
+ WPanel root = description.getRootPanel();
+ if (root != null) root.addPainters();
description.addPainters();
reposition(screenWidth, screenHeight);
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/client/modmenu/ConfigGui.java b/src/main/java/io/github/cottonmc/cotton/gui/client/modmenu/ConfigGui.java
index ccc9679..cd3fe0a 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/client/modmenu/ConfigGui.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/client/modmenu/ConfigGui.java
@@ -17,7 +17,7 @@ public class ConfigGui extends LightweightGuiDescription {
public ConfigGui(Screen previous) {
WGridPanel root = new WGridPanel();
setRootPanel(root);
-
+
WToggleButton darkmodeButton = new WToggleButton(new TranslatableText("option.libgui.darkmode")) {
@Override
public void onToggle(boolean on) {
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/client/modmenu/ModMenuSupport.java b/src/main/java/io/github/cottonmc/cotton/gui/client/modmenu/ModMenuSupport.java
index 5cec105..29ea6bf 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/client/modmenu/ModMenuSupport.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/client/modmenu/ModMenuSupport.java
@@ -21,11 +21,6 @@ public class ModMenuSupport implements ModMenuApi {
public void onClose() {
this.client.openScreen(screen);
}
-
- protected void init() {
- super.init();
- this.description.getRootPanel().validate(null);
- };
};
}
}
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/impl/access/SlotAccessor.java b/src/main/java/io/github/cottonmc/cotton/gui/impl/access/SlotAccessor.java
new file mode 100644
index 0000000..954b4a8
--- /dev/null
+++ b/src/main/java/io/github/cottonmc/cotton/gui/impl/access/SlotAccessor.java
@@ -0,0 +1,14 @@
+package io.github.cottonmc.cotton.gui.impl.access;
+
+import net.minecraft.screen.slot.Slot;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+@Mixin(Slot.class)
+public interface SlotAccessor {
+ @Accessor("x")
+ void setX(int x);
+
+ @Accessor("y")
+ void setY(int y);
+}
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/WCardPanel.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/WCardPanel.java
new file mode 100644
index 0000000..6c6449a
--- /dev/null
+++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/WCardPanel.java
@@ -0,0 +1,189 @@
+package io.github.cottonmc.cotton.gui.widget;
+
+import io.github.cottonmc.cotton.gui.GuiDescription;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+/**
+ * Similar to the CardLayout in AWT, this panel displays one widget at a time from a list of widgets.
+ *
+ * @since 3.0.0
+ */
+public class WCardPanel extends WPanel {
+ private final List<WWidget> cards = new ArrayList<>();
+ private int selectedIndex = 0;
+
+ /**
+ * Adds a card to this panel without resizing it.
+ *
+ * @param card the added card
+ */
+ public void add(WWidget card) {
+ add(cards.size(), card);
+ }
+
+ /**
+ * Adds a card to this panel without resizing it.
+ *
+ * @param index the index of the card
+ * @param card the added card
+ */
+ public void add(int index, WWidget card) {
+ cards.add(index, card);
+
+ card.setParent(this);
+ card.setLocation(0, 0);
+ expandToFit(card);
+ }
+
+ /**
+ * Adds a card to this panel and resizes it.
+ *
+ * @param card the added card
+ * @param width the new width
+ * @param height the new height
+ */
+ public void add(WWidget card, int width, int height) {
+ add(cards.size(), card, width, height);
+ }
+
+ /**
+ * Adds a card to this panel and resizes it.
+ *
+ * @param index the index of the card
+ * @param card the added card
+ * @param width the new width
+ * @param height the new height
+ */
+ public void add(int index, WWidget card, int width, int height) {
+ if (card.canResize()) {
+ card.setSize(width, height);
+ }
+
+ add(index, card);
+ }
+
+ /**
+ * Gets the index of the selected card in this panel.
+ *
+ * @return the selected card's index
+ */
+ public int getSelectedIndex() {
+ return selectedIndex;
+ }
+
+ /**
+ * Sets the selected index of this panel.
+ *
+ * @param selectedIndex the new selected index
+ * @return this panel
+ * @throws IndexOutOfBoundsException if this panel does not contain the card index
+ */
+ public WCardPanel setSelectedIndex(int selectedIndex) {
+ if (selectedIndex < 0 || selectedIndex >= cards.size()) {
+ throw new IndexOutOfBoundsException("Card index " + selectedIndex + " out of bounds: 0 <= index <" + cards.size());
+ }
+
+ if (this.selectedIndex != selectedIndex) {
+ this.selectedIndex = selectedIndex;
+ layout();
+ }
+
+ return this;
+ }
+
+ /**
+ * Gets the selected card of this panel.
+ *
+ * @return the selected card
+ */
+ public WWidget getSelectedCard() {
+ return cards.get(getSelectedIndex());
+ }
+
+ /**
+ * Sets the selected card of this panel.
+ *
+ * @param selectedCard the new selected card
+ * @return this panel
+ * @throws NoSuchElementException if this panel does not contain the card
+ */
+ public WCardPanel setSelectedCard(WWidget selectedCard) {
+ if (!cards.contains(selectedCard)) {
+ throw new NoSuchElementException("Widget " + selectedCard + " is not a card in this panel!");
+ }
+
+ return setSelectedIndex(cards.indexOf(selectedCard));
+ }
+
+ @Override
+ public void setSize(int x, int y) {
+ super.setSize(x, y);
+ for (WWidget card : cards) {
+ card.setSize(x, y);
+ }
+ }
+
+ @Override
+ public void layout() {
+ children.clear();
+
+ for (WWidget child : cards) {
+ if (child instanceof WPanel) ((WPanel) child).layout();
+ expandToFit(child);
+
+ if (child == getSelectedCard()) {
+ child.onShown();
+ } else {
+ child.onHidden();
+ }
+ }
+
+ for (WWidget child : cards) {
+ child.setSize(getWidth(), getHeight());
+ }
+
+ children.add(getSelectedCard());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @param c the host GUI description
+ * @throws IllegalStateException if this panel has no cards
+ */
+ @Override
+ public void validate(GuiDescription c) {
+ if (cards.isEmpty()) {
+ throw new IllegalStateException("No children in card panel");
+ }
+
+ layout();
+ for (WWidget card : cards) {
+ card.validate(c);
+ if (getSelectedCard() != card) card.onHidden();
+ }
+
+ if (c != null) createPeers(c);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void createPeers(GuiDescription c) {
+ for (WWidget card : cards) {
+ card.createPeers(c);
+ }
+ }
+
+ @Environment(EnvType.CLIENT)
+ @Override
+ public void addPainters() {
+ for (WWidget card : cards) {
+ card.addPainters();
+ }
+ }
+}
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/WItemSlot.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/WItemSlot.java
index 4ef0056..15e6916 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/widget/WItemSlot.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/WItemSlot.java
@@ -61,7 +61,6 @@ public class WItemSlot extends WWidget {
private final List<ValidatedSlot> peers = new ArrayList<>();
@Nullable
@Environment(EnvType.CLIENT)
- // TODO: Set the background painter to SLOT in a new method that sets a widget's default painter.
private BackgroundPainter backgroundPainter = null;
private Inventory inventory;
private int startIndex = 0;
@@ -328,7 +327,9 @@ public class WItemSlot extends WWidget {
@Environment(EnvType.CLIENT)
@Override
public void paint(MatrixStack matrices, int x, int y, int mouseX, int mouseY) {
- (backgroundPainter != null ? backgroundPainter : BackgroundPainter.SLOT).paintBackground(x, y, this);
+ if (backgroundPainter != null) {
+ backgroundPainter.paintBackground(x, y, this);
+ }
}
@Nullable
@@ -370,6 +371,28 @@ public class WItemSlot extends WWidget {
}
}
+ @Override
+ public void onShown() {
+ for (ValidatedSlot peer : peers) {
+ peer.setVisible(true);
+ }
+ }
+
+ @Override
+ public void onHidden() {
+ super.onHidden();
+
+ for (ValidatedSlot peer : peers) {
+ peer.setVisible(false);
+ }
+ }
+
+ @Environment(EnvType.CLIENT)
+ @Override
+ public void addPainters() {
+ backgroundPainter = BackgroundPainter.SLOT;
+ }
+
/**
* A listener for changes in an item slot.
*
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/WPanel.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/WPanel.java
index 10725ee..7265e8d 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/widget/WPanel.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/WPanel.java
@@ -279,7 +279,38 @@ public abstract class WPanel extends WWidget {
}
@Override
+ public void onShown() {
+ for (WWidget child : children) {
+ child.onShown();
+ }
+ }
+
+ @Override
+ public void onHidden() {
+ super.onHidden();
+
+ for (WWidget child : children) {
+ child.onHidden();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>Subclasses should call {@code super.addPainters()} to ensure that children have proper default painters.
+ *
+ * @since 3.0.0
+ */
+ @Environment(EnvType.CLIENT)
+ @Override
+ public void addPainters() {
+ for (WWidget child : children) {
+ child.addPainters();
+ }
+ }
+
+ @Override
public String toString() {
- return "WPanel{ children: [\n" + children.stream().map(Objects::toString).flatMap(x -> Stream.of(x.split("\n")).map(y -> "\t" + y)).collect(Collectors.joining(",\n")) + "] }";
+ return getClass().getSimpleName() + " {\n" + children.stream().map(Object::toString).map(x -> x + ",").flatMap(x -> Stream.of(x.split("\n")).filter(y -> !y.isEmpty()).map(y -> "\t" + y)).collect(Collectors.joining("\n")) + "\n}";
}
}
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/WTabPanel.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/WTabPanel.java
new file mode 100644
index 0000000..039f2bd
--- /dev/null
+++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/WTabPanel.java
@@ -0,0 +1,370 @@
+package io.github.cottonmc.cotton.gui.widget;
+
+import io.github.cottonmc.cotton.gui.client.BackgroundPainter;
+import io.github.cottonmc.cotton.gui.client.LibGuiClient;
+import io.github.cottonmc.cotton.gui.client.ScreenDrawing;
+import io.github.cottonmc.cotton.gui.widget.data.Axis;
+import io.github.cottonmc.cotton.gui.widget.data.HorizontalAlignment;
+import io.github.cottonmc.cotton.gui.widget.icon.Icon;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.sound.PositionedSoundInstance;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.sound.SoundEvents;
+import net.minecraft.text.Text;
+import net.minecraft.util.Identifier;
+
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+// TODO: Different tab positions
+
+/**
+ * A panel that contains creative inventory-style tabs on the top.
+ *
+ * @since 3.0.0
+ */
+public class WTabPanel extends WPanel {
+ private static final int TAB_PADDING = 4;
+ private static final int TAB_WIDTH = 28;
+ private static final int TAB_HEIGHT = 30;
+ private static final int PANEL_PADDING = 8; // The padding of BackgroundPainter.VANILLA
+ private static final int ICON_SIZE = 16;
+ private final WBox tabRibbon = new WBox(Axis.HORIZONTAL).setSpacing(1);
+ private final List<WTab> tabWidgets = new ArrayList<>();
+ private final WCardPanel mainPanel = new WCardPanel();
+
+ /**
+ * Constructs a new tab panel.
+ */
+ public WTabPanel() {
+ add(tabRibbon, 0, 0);
+ add(mainPanel, PANEL_PADDING, TAB_HEIGHT + PANEL_PADDING);
+ }
+
+ private void add(WWidget widget, int x, int y) {
+ children.add(widget);
+ widget.setParent(this);
+ widget.setLocation(x, y);
+ expandToFit(widget);
+ }
+
+ /**
+ * Adds a tab to this panel.
+ *
+ * @param tab the added tab
+ */
+ public void add(Tab tab) {
+ WTab tabWidget = new WTab(tab);
+
+ if (tabWidgets.isEmpty()) {
+ tabWidget.selected = true;
+ }
+
+ tabWidgets.add(tabWidget);
+ tabRibbon.add(tabWidget, TAB_WIDTH, TAB_HEIGHT + TAB_PADDING);
+ mainPanel.add(tab.getWidget());
+ }
+
+ /**
+ * Configures and adds a tab to this panel.
+ *
+ * @param widget the contained widget
+ * @param configurator the tab configurator
+ */
+ public void add(WWidget widget, Consumer<Tab.Builder> configurator) {
+ Tab.Builder builder = new Tab.Builder(widget);
+ configurator.accept(builder);
+ add(builder.build());
+ }
+
+ @Override
+ public void setSize(int x, int y) {
+ super.setSize(x, y);
+ tabRibbon.setSize(x, TAB_HEIGHT);
+ }
+
+ @Environment(EnvType.CLIENT)
+ @Override
+ public void addPainters() {
+ super.addPainters();
+ mainPanel.setBackgroundPainter(BackgroundPainter.VANILLA);
+ }
+
+ /**
+ * The data of a tab.
+ */
+ public static class Tab {
+ @Nullable
+ private final Text title;
+ @Nullable
+ private final Icon icon;
+ private final WWidget widget;
+ private final Consumer<TooltipBuilder> tooltip;
+
+ /**
+ * Constructs a tab.
+ *
+ * @param title the tab title
+ * @param icon the tab icon
+ * @param widget the widget contained in the tab
+ * @param tooltip the tab tooltip
+ * @throws IllegalArgumentException if both the title and the icon are null
+ * @throws NullPointerException if either the widget or the tooltip is null
+ */
+ public Tab(@Nullable Text title, @Nullable Icon icon, WWidget widget, Consumer<TooltipBuilder> tooltip) {
+ if (title == null && icon == null) {
+ throw new IllegalArgumentException("A tab must have a title or an icon");
+ }
+
+ this.title = title;
+ this.icon = icon;
+ this.widget = Objects.requireNonNull(widget, "widget");
+ this.tooltip = Objects.requireNonNull(tooltip, "tooltip");
+ }
+
+ /**
+ * Gets the title of this tab.
+ *
+ * @return the title, or null if there's no title
+ */
+ @Nullable
+ public Text getTitle() {
+ return title;
+ }
+
+ /**
+ * Gets the icon of this tab.
+ *
+ * @return the icon, or null if there's no title
+ */
+ @Nullable
+ public Icon getIcon() {
+ return icon;
+ }
+
+ /**
+ * Gets the contained widget of this tab.
+ *
+ * @return the contained widget
+ */
+ public WWidget getWidget() {
+ return widget;
+ }
+
+ /**
+ * Adds this widget's tooltip to the {@code tooltip} builder.
+ *
+ * @param tooltip the tooltip builder
+ */
+ public void addTooltip(TooltipBuilder tooltip) {
+ this.tooltip.accept(tooltip);
+ }
+
+ /**
+ * A builder for tab data.
+ */
+ public static final class Builder {
+ @Nullable
+ private Text title;
+ @Nullable
+ private Icon icon;
+ private final WWidget widget;
+ private final List<Consumer<TooltipBuilder>> tooltip = new ArrayList<>();
+ private static final Consumer<TooltipBuilder> DEFAULT_TOOLTIP = builder -> {};
+
+ /**
+ * Constructs a new tab data builder.
+ *
+ * @param widget the contained widget
+ * @throws NullPointerException if the widget is null
+ */
+ public Builder(WWidget widget) {
+ this.widget = Objects.requireNonNull(widget, "widget");
+ }
+
+ /**
+ * Sets the tab title.
+ *
+ * @param title the new title
+ * @return this builder
+ * @throws NullPointerException if the title is null
+ */
+ public Builder title(Text title) {
+ this.title = Objects.requireNonNull(title, "title");
+ return this;
+ }
+
+ /**
+ * Sets the tab icon.
+ *
+ * @param icon the new icon
+ * @return this builder
+ * @throws NullPointerException if the icon is null
+ */
+ public Builder icon(Icon icon) {
+ this.icon = Objects.requireNonNull(icon, "icon");
+ return this;
+ }
+
+ /**
+ * Adds lines to the tab's tooltip.
+ *
+ * @param lines the added lines
+ * @return this builder
+ * @throws NullPointerException if the line array is null
+ */
+ public Builder tooltip(Text... lines) {
+ Objects.requireNonNull(lines, "lines");
+ tooltip.add(builder -> builder.add(lines));
+
+ return this;
+ }
+
+ /**
+ * Adds lines to the tab's tooltip.
+ *
+ * @param lines the added lines
+ * @return this builder
+ * @throws NullPointerException if the line collection is null
+ */
+ public Builder tooltip(Collection<? extends Text> lines) {
+ Objects.requireNonNull(lines, "lines");
+ tooltip.add(builder -> builder.add(lines.toArray(new Text[0])));
+ return this;
+ }
+
+ /**
+ * Builds a tab from this builder.
+ *
+ * @return the built tab
+ * @see Tab#Tab(Text, Icon, WWidget, Consumer)
+ */
+ public Tab build() {
+ Consumer<TooltipBuilder> tooltip = DEFAULT_TOOLTIP;
+
+ if (!this.tooltip.isEmpty()) {
+ tooltip = builder -> {
+ for (Consumer<TooltipBuilder> entry : this.tooltip) {
+ entry.accept(builder);
+ }
+ };
+ }
+
+ return new Tab(title, icon, widget, tooltip);
+ }
+ }
+ }
+
+ private final class WTab extends WWidget {
+ private final Tab data;
+ boolean selected = false;
+
+ WTab(Tab data) {
+ this.data = data;
+ }
+
+ @Override
+ public boolean canFocus() {
+ return true;
+ }
+
+ @Environment(EnvType.CLIENT)
+ @Override
+ public void onClick(int x, int y, int button) {
+ super.onClick(x, y, button);
+
+ MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F));
+
+ for (WTab tab : tabWidgets) {
+ tab.selected = (tab == this);
+ }
+
+ mainPanel.setSelectedCard(data.getWidget());
+ WTabPanel.this.layout();
+ }
+
+ @Environment(EnvType.CLIENT)
+ @Override
+ public void onKeyPressed(int ch, int key, int modifiers) {
+ if (isActivationKey(ch)) {
+ onClick(0, 0, 0);
+ }
+ }
+
+ @Environment(EnvType.CLIENT)
+ @Override
+ public void paint(MatrixStack matrices, int x, int y, int mouseX, int mouseY) {
+ TextRenderer renderer = MinecraftClient.getInstance().textRenderer;
+ Text title = data.getTitle();
+ Icon icon = data.getIcon();
+
+ if (title != null) {
+ int width = TAB_WIDTH + renderer.getWidth(title);
+ if (icon == null) width = Math.max(TAB_WIDTH, width - ICON_SIZE);
+
+ if (this.width != width) {
+ setSize(width, this.height);
+ getParent().layout();
+ }
+ }
+
+ (selected ? Painters.SELECTED_TAB : Painters.UNSELECTED_TAB).paintBackground(x, y, this);
+ if (isFocused()) {
+ (selected ? Painters.SELECTED_TAB_FOCUS_BORDER : Painters.UNSELECTED_TAB_FOCUS_BORDER).paintBackground(x, y, this);
+ }
+
+ int iconX = 6;
+
+ if (title != null) {
+ int titleX = (icon != null) ? iconX + ICON_SIZE + 1 : 0;
+ int titleY = (height - TAB_PADDING - renderer.fontHeight) / 2 + 1;
+ int width = (icon != null) ? this.width - iconX - ICON_SIZE : this.width;
+ HorizontalAlignment align = (icon != null) ? HorizontalAlignment.LEFT : HorizontalAlignment.CENTER;
+
+ int color;
+ if (LibGuiClient.config.darkMode) {
+ color = WLabel.DEFAULT_DARKMODE_TEXT_COLOR;
+ } else {
+ color = selected ? WLabel.DEFAULT_TEXT_COLOR : 0xEEEEEE;
+ }
+
+ ScreenDrawing.drawString(matrices, title.asOrderedText(), align, x + titleX, y + titleY, width, color);
+ }
+
+ if (icon != null) {
+ icon.paint(matrices, x + iconX, y + (height - TAB_PADDING - ICON_SIZE) / 2, ICON_SIZE);
+ }
+ }
+
+ @Override
+ public void addTooltip(TooltipBuilder tooltip) {
+ data.addTooltip(tooltip);
+ }
+ }
+
+ /**
+ * Internal background painter instances for tabs.
+ */
+ @Environment(EnvType.CLIENT)
+ final static class Painters {
+ static final BackgroundPainter SELECTED_TAB = BackgroundPainter.createLightDarkVariants(
+ BackgroundPainter.createNinePatch(new Identifier("libgui", "textures/widget/tab/selected_light.png")).setTopPadding(2),
+ BackgroundPainter.createNinePatch(new Identifier("libgui", "textures/widget/tab/selected_dark.png")).setTopPadding(2)
+ );
+
+ static final BackgroundPainter UNSELECTED_TAB = BackgroundPainter.createLightDarkVariants(
+ BackgroundPainter.createNinePatch(new Identifier("libgui", "textures/widget/tab/unselected_light.png")),
+ BackgroundPainter.createNinePatch(new Identifier("libgui", "textures/widget/tab/unselected_dark.png"))
+ );
+
+ static final BackgroundPainter SELECTED_TAB_FOCUS_BORDER = BackgroundPainter.createNinePatch(new Identifier("libgui", "textures/widget/tab/focus.png")).setTopPadding(2);
+ static final BackgroundPainter UNSELECTED_TAB_FOCUS_BORDER = BackgroundPainter.createNinePatch(new Identifier("libgui", "textures/widget/tab/focus.png"));
+ }
+}
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/WWidget.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/WWidget.java
index 789560a..ee9c1e7 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/widget/WWidget.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/WWidget.java
@@ -3,6 +3,7 @@ package io.github.cottonmc.cotton.gui.widget;
import java.util.ArrayList;
import java.util.List;
+import com.google.common.annotations.Beta;
import io.github.cottonmc.cotton.gui.GuiDescription;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
@@ -372,7 +373,11 @@ public class WWidget {
* @param host the host GUI description
*/
public void validate(GuiDescription host) {
- this.host = host;
+ if (host != null) {
+ this.host = host;
+ } else {
+ LOGGER.warn("Validating {} with a null host", this);
+ }
}
/**
@@ -435,6 +440,42 @@ public class WWidget {
}
/**
+ * Notifies this widget that it is visible and
+ * shows any hidden peers of itself and its children.
+ *
+ * @since 3.0.0
+ */
+ @Beta
+ public void onShown() {
+ }
+
+ /**
+ * Notifies this widget that it won't be drawn and
+ * hides any visible peers of itself and its children.
+ *
+ * <p>The default implementation releases this widget's
+ * focus if it is focused. Overriding implementations
+ * might want to do this as well.
+ *
+ * @since 3.0.0
+ */
+ @Beta
+ public void onHidden() {
+ releaseFocus();
+ }
+
+ /**
+ * Adds the default background painters to this widget and all children.
+ *
+ * <p>Always called before {@link GuiDescription#addPainters()} to allow users to modify painters.
+ *
+ * @since 3.0.0
+ */
+ @Environment(EnvType.CLIENT)
+ public void addPainters() {
+ }
+
+ /**
* Tests if the provided key code is an activation key for widgets.
*
* <p>The activation keys are Enter, keypad Enter, and Space.
diff --git a/src/main/resources/assets/libgui/textures/widget/tab/focus.png b/src/main/resources/assets/libgui/textures/widget/tab/focus.png
new file mode 100644
index 0000000..6d25685
--- /dev/null
+++ b/src/main/resources/assets/libgui/textures/widget/tab/focus.png
Binary files differ
diff --git a/src/main/resources/assets/libgui/textures/widget/tab/selected_dark.png b/src/main/resources/assets/libgui/textures/widget/tab/selected_dark.png
new file mode 100644
index 0000000..24ba766
--- /dev/null
+++ b/src/main/resources/assets/libgui/textures/widget/tab/selected_dark.png
Binary files differ
diff --git a/src/main/resources/assets/libgui/textures/widget/tab/selected_light.png b/src/main/resources/assets/libgui/textures/widget/tab/selected_light.png
new file mode 100644
index 0000000..ce7cbf2
--- /dev/null
+++ b/src/main/resources/assets/libgui/textures/widget/tab/selected_light.png
Binary files differ
diff --git a/src/main/resources/assets/libgui/textures/widget/tab/unselected_dark.png b/src/main/resources/assets/libgui/textures/widget/tab/unselected_dark.png
new file mode 100644
index 0000000..ae1d16e
--- /dev/null
+++ b/src/main/resources/assets/libgui/textures/widget/tab/unselected_dark.png
Binary files differ
diff --git a/src/main/resources/assets/libgui/textures/widget/tab/unselected_light.png b/src/main/resources/assets/libgui/textures/widget/tab/unselected_light.png
new file mode 100644
index 0000000..21ab044
--- /dev/null
+++ b/src/main/resources/assets/libgui/textures/widget/tab/unselected_light.png
Binary files differ
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index 22d9990..df75753 100644
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -21,6 +21,9 @@
"client": ["io.github.cottonmc.cotton.gui.client.LibGuiClient"],
"modmenu": ["io.github.cottonmc.cotton.gui.client.modmenu.ModMenuSupport"]
},
+ "mixins": [
+ "mixins.libgui.accessors.json"
+ ],
"depends": {
"fabricloader": ">=0.8.8",
"fabric": "*",
diff --git a/src/main/resources/mixins.libgui.accessors.json b/src/main/resources/mixins.libgui.accessors.json
new file mode 100644
index 0000000..9ec55f2
--- /dev/null
+++ b/src/main/resources/mixins.libgui.accessors.json
@@ -0,0 +1,14 @@
+{
+ "compatibilityLevel": "JAVA_8",
+ "minVersion": "0.7.11",
+ "package": "io.github.cottonmc.cotton.gui.impl.access",
+ "required": true,
+
+ "mixins": [
+ "SlotAccessor"
+ ],
+
+ "injectors": {
+ "defaultRequire": 1
+ }
+}