aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJuuz <6596629+Juuxel@users.noreply.github.com>2023-03-18 20:07:12 +0200
committerJuuz <6596629+Juuxel@users.noreply.github.com>2023-03-18 20:07:12 +0200
commit6835bb564b28075dcf05605560827d92994f44d9 (patch)
tree15579df3daf6fb02ec5b85848731028920fb2785
parent8912cc45001f7c580da2a90218a47a8ada80aa70 (diff)
downloadLibGui-6835bb564b28075dcf05605560827d92994f44d9.tar.gz
LibGui-6835bb564b28075dcf05605560827d92994f44d9.tar.bz2
LibGui-6835bb564b28075dcf05605560827d92994f44d9.zip
Update to 1.19.4, add new focus API
-rw-r--r--GuiTest/src/main/java/io/github/cottonmc/test/LibGuiTest.java6
-rw-r--r--gradle.properties14
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/GuiDescription.java11
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/SyncedGuiDescription.java4
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/client/CottonClientScreen.java51
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/client/CottonInventoryScreen.java51
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/impl/FocusHandler.java43
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/impl/client/FocusElements.java156
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/impl/mixin/client/ScreenAccessor.java15
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/impl/mixin/client/package-info.java4
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WAbstractSlider.java9
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WButton.java5
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WItem.java4
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WItemSlot.java65
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WPanel.java68
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WScrollBar.java6
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WTabPanel.java5
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WTextField.java22
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WToggleButton.java5
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/WWidget.java29
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/data/InputResult.java2
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/data/Rect2i.java13
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/focus/Focus.java27
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/focus/FocusHandler.java50
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/focus/SimpleFocusHandler.java23
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/focus/package-info.java4
-rw-r--r--src/main/java/io/github/cottonmc/cotton/gui/widget/icon/ItemIcon.java15
-rw-r--r--src/main/resources/fabric.mod.json9
-rw-r--r--src/main/resources/mixins.libgui.json14
29 files changed, 489 insertions, 241 deletions
diff --git a/GuiTest/src/main/java/io/github/cottonmc/test/LibGuiTest.java b/GuiTest/src/main/java/io/github/cottonmc/test/LibGuiTest.java
index 009ea23..3c11004 100644
--- a/GuiTest/src/main/java/io/github/cottonmc/test/LibGuiTest.java
+++ b/GuiTest/src/main/java/io/github/cottonmc/test/LibGuiTest.java
@@ -13,6 +13,8 @@ import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
+import net.minecraft.resource.featuretoggle.FeatureFlags;
+import net.minecraft.resource.featuretoggle.FeatureSet;
import net.minecraft.screen.ScreenHandlerContext;
import net.minecraft.screen.ScreenHandlerType;
import net.minecraft.util.Identifier;
@@ -48,10 +50,10 @@ public class LibGuiTest implements ModInitializer {
GUI_SCREEN_HANDLER_TYPE = new ScreenHandlerType<>((int syncId, PlayerInventory inventory) -> {
return new TestDescription(GUI_SCREEN_HANDLER_TYPE, syncId, inventory, ScreenHandlerContext.EMPTY);
- });
+ }, FeatureSet.of(FeatureFlags.VANILLA));
Registry.register(Registries.SCREEN_HANDLER, new Identifier(MODID, "gui"), GUI_SCREEN_HANDLER_TYPE);
- REALLY_SIMPLE_SCREEN_HANDLER_TYPE = new ScreenHandlerType<>(ReallySimpleDescription::new);
+ REALLY_SIMPLE_SCREEN_HANDLER_TYPE = new ScreenHandlerType<>(ReallySimpleDescription::new, FeatureSet.of(FeatureFlags.VANILLA));
Registry.register(Registries.SCREEN_HANDLER, new Identifier(MODID, "really_simple"), REALLY_SIMPLE_SCREEN_HANDLER_TYPE);
Optional<ModContainer> containerOpt = FabricLoader.getInstance().getModContainer("jankson");
diff --git a/gradle.properties b/gradle.properties
index 1fe8e59..7476692 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -4,17 +4,17 @@ fabric.loom.multiProjectOptimisation = true
# Fabric Properties
# check these on https://fabricmc.net/use
- minecraft_version=1.19.3
- yarn_mappings=1.19.3+build.2
- loader_version=0.14.11
+ minecraft_version=1.19.4
+ yarn_mappings=1.19.4+build.1
+ loader_version=0.14.17
# Mod Properties
- mod_version = 6.5.3
+ mod_version = 7.0.0-beta.1
maven_group = io.github.cottonmc
archives_base_name = LibGui
# Dependencies
- fabric_version=0.76.0+1.19.3
- jankson_version=5.0.0+j1.2.1
- modmenu_version=5.0.2
+ fabric_version=0.76.0+1.19.4
+ jankson_version=5.0.1+j1.2.2
+ modmenu_version=6.1.0-rc.4
libninepatch_version=1.2.0
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/GuiDescription.java b/src/main/java/io/github/cottonmc/cotton/gui/GuiDescription.java
index 5962e69..6435651 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/GuiDescription.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/GuiDescription.java
@@ -4,7 +4,6 @@ import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.screen.PropertyDelegate;
-import io.github.cottonmc.cotton.gui.impl.FocusHandler;
import io.github.cottonmc.cotton.gui.widget.WPanel;
import io.github.cottonmc.cotton.gui.widget.WWidget;
import io.github.cottonmc.cotton.gui.widget.data.HorizontalAlignment;
@@ -77,16 +76,6 @@ public interface GuiDescription {
public void releaseFocus(WWidget widget);
/**
- * Cycles the focused widget in the GUI.
- *
- * @param lookForwards whether this should cycle forwards (true) or backwards (false)
- * @since 2.0.0
- */
- default void cycleFocus(boolean lookForwards) {
- FocusHandler.cycleFocus(this, lookForwards);
- }
-
- /**
* Gets whether this GUI is fullscreen.
*
* <p>Fullscreen GUIs have no default background painter and
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/SyncedGuiDescription.java b/src/main/java/io/github/cottonmc/cotton/gui/SyncedGuiDescription.java
index 15917ff..4a92003 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/SyncedGuiDescription.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/SyncedGuiDescription.java
@@ -475,8 +475,8 @@ public class SyncedGuiDescription extends ScreenHandler implements GuiDescriptio
}
@Override
- public void close(PlayerEntity player) {
- super.close(player);
+ public void onClosed(PlayerEntity player) {
+ super.onClosed(player);
if (blockInventory != null) blockInventory.onClose(player);
}
//}
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 524e73d..81d7646 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
@@ -1,5 +1,6 @@
package io.github.cottonmc.cotton.gui.client;
+import net.minecraft.client.gui.Element;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder;
import net.minecraft.client.util.math.MatrixStack;
@@ -10,15 +11,18 @@ import net.minecraft.text.Text;
import io.github.cottonmc.cotton.gui.GuiDescription;
import io.github.cottonmc.cotton.gui.impl.VisualLogger;
import io.github.cottonmc.cotton.gui.impl.client.CottonScreenImpl;
+import io.github.cottonmc.cotton.gui.impl.client.FocusElements;
import io.github.cottonmc.cotton.gui.impl.client.MouseInputHandler;
import io.github.cottonmc.cotton.gui.impl.client.NarrationHelper;
+import io.github.cottonmc.cotton.gui.impl.mixin.client.ScreenAccessor;
import io.github.cottonmc.cotton.gui.widget.WPanel;
import io.github.cottonmc.cotton.gui.widget.WWidget;
+import io.github.cottonmc.cotton.gui.widget.data.InputResult;
import org.jetbrains.annotations.Nullable;
-import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL11;
public class CottonClientScreen extends Screen implements CottonScreenImpl {
+ private static final VisualLogger LOGGER = new VisualLogger(CottonInventoryScreen.class);
protected GuiDescription description;
protected int left = 0;
protected int top = 0;
@@ -67,6 +71,14 @@ public class CottonClientScreen extends Screen implements CottonScreenImpl {
if (root != null) root.addPainters();
description.addPainters();
reposition(width, height);
+
+ if (root != null) {
+ Element rootPanelElement = FocusElements.ofPanel(root);
+ ((ScreenAccessor) this).libgui$getChildren().add(rootPanelElement);
+ setInitialFocus(rootPanelElement);
+ } else {
+ LOGGER.warn("No root panel found, keyboard navigation disabled");
+ }
}
@Override
@@ -217,28 +229,32 @@ public class CottonClientScreen extends Screen implements CottonScreenImpl {
@Override
public boolean charTyped(char ch, int keyCode) {
- if (description.getFocus()==null) return super.charTyped(ch, keyCode);
- description.getFocus().onCharTyped(ch);
- return true;
+ WWidget focus = description.getFocus();
+ if (focus != null && focus.onCharTyped(ch) == InputResult.PROCESSED) {
+ return true;
+ }
+
+ return super.charTyped(ch, keyCode);
}
@Override
public boolean keyPressed(int ch, int keyCode, int modifiers) {
- if (ch == GLFW.GLFW_KEY_ESCAPE || ch == GLFW.GLFW_KEY_TAB) {
- // special hardcoded keys, these will never be delivered to widgets
- return super.keyPressed(ch, keyCode, modifiers);
- } else {
- if (description.getFocus() == null) return super.keyPressed(ch, keyCode, modifiers);
- description.getFocus().onKeyPressed(ch, keyCode, modifiers);
+ WWidget focus = description.getFocus();
+ if (focus != null && focus.onKeyPressed(ch, keyCode, modifiers) == InputResult.PROCESSED) {
return true;
}
+
+ return super.keyPressed(ch, keyCode, modifiers);
}
@Override
public boolean keyReleased(int ch, int keyCode, int modifiers) {
- if (description.getFocus()==null) return super.keyReleased(ch, keyCode, modifiers);
- description.getFocus().onKeyReleased(ch, keyCode, modifiers);
- return true;
+ WWidget focus = description.getFocus();
+ if (focus != null && focus.onKeyReleased(ch, keyCode, modifiers) == InputResult.PROCESSED) {
+ return true;
+ }
+
+ return super.keyReleased(ch, keyCode, modifiers);
}
@Override
@@ -247,15 +263,6 @@ public class CottonClientScreen extends Screen implements CottonScreenImpl {
}
@Override
- public boolean changeFocus(boolean lookForwards) {
- if (description != null) {
- description.cycleFocus(lookForwards);
- }
-
- return true;
- }
-
- @Override
protected void addElementNarrations(NarrationMessageBuilder builder) {
if (description != null) NarrationHelper.addNarrations(description.getRootPanel(), builder);
}
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 6406619..61fc287 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
@@ -1,5 +1,6 @@
package io.github.cottonmc.cotton.gui.client;
+import net.minecraft.client.gui.Element;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder;
import net.minecraft.client.render.DiffuseLighting;
@@ -14,13 +15,15 @@ import io.github.cottonmc.cotton.gui.GuiDescription;
import io.github.cottonmc.cotton.gui.SyncedGuiDescription;
import io.github.cottonmc.cotton.gui.impl.VisualLogger;
import io.github.cottonmc.cotton.gui.impl.client.CottonScreenImpl;
+import io.github.cottonmc.cotton.gui.impl.client.FocusElements;
import io.github.cottonmc.cotton.gui.impl.client.MouseInputHandler;
import io.github.cottonmc.cotton.gui.impl.client.NarrationHelper;
+import io.github.cottonmc.cotton.gui.impl.mixin.client.ScreenAccessor;
import io.github.cottonmc.cotton.gui.widget.WPanel;
import io.github.cottonmc.cotton.gui.widget.WWidget;
+import io.github.cottonmc.cotton.gui.widget.data.InputResult;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
-import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL11;
/**
@@ -29,6 +32,7 @@ import org.lwjgl.opengl.GL11;
* @param <T> the description type
*/
public class CottonInventoryScreen<T extends SyncedGuiDescription> extends HandledScreen<T> implements CottonScreenImpl {
+ private static final VisualLogger LOGGER = new VisualLogger(CottonInventoryScreen.class);
protected SyncedGuiDescription description;
@Nullable protected WWidget lastResponder = null;
private final MouseInputHandler<CottonInventoryScreen<T>> mouseInputHandler = new MouseInputHandler<>(this);
@@ -102,6 +106,14 @@ public class CottonInventoryScreen<T extends SyncedGuiDescription> extends Handl
description.addPainters();
reposition(width, height);
+
+ if (root != null) {
+ Element rootPanelElement = FocusElements.ofPanel(root);
+ ((ScreenAccessor) this).libgui$getChildren().add(rootPanelElement);
+ setInitialFocus(rootPanelElement);
+ } else {
+ LOGGER.warn("No root panel found, keyboard navigation disabled");
+ }
}
@Override
@@ -233,28 +245,32 @@ public class CottonInventoryScreen<T extends SyncedGuiDescription> extends Handl
@Override
public boolean charTyped(char ch, int keyCode) {
- if (description.getFocus()==null) return super.charTyped(ch, keyCode);
- description.getFocus().onCharTyped(ch);
- return true;
+ WWidget focus = description.getFocus();
+ if (focus != null && focus.onCharTyped(ch) == InputResult.PROCESSED) {
+ return true;
+ }
+
+ return super.charTyped(ch, keyCode);
}
@Override
public boolean keyPressed(int ch, int keyCode, int modifiers) {
- if (ch == GLFW.GLFW_KEY_ESCAPE || ch == GLFW.GLFW_KEY_TAB) {
- // special hardcoded keys, these will never be delivered to widgets
- return super.keyPressed(ch, keyCode, modifiers);
- } else {
- if (description.getFocus() == null) return super.keyPressed(ch, keyCode, modifiers);
- description.getFocus().onKeyPressed(ch, keyCode, modifiers);
+ WWidget focus = description.getFocus();
+ if (focus != null && focus.onKeyPressed(ch, keyCode, modifiers) == InputResult.PROCESSED) {
return true;
}
+
+ return super.keyPressed(ch, keyCode, modifiers);
}
@Override
public boolean keyReleased(int ch, int keyCode, int modifiers) {
- if (description.getFocus()==null) return super.keyReleased(ch, keyCode, modifiers);
- description.getFocus().onKeyReleased(ch, keyCode, modifiers);
- return true;
+ WWidget focus = description.getFocus();
+ if (focus != null && focus.onKeyReleased(ch, keyCode, modifiers) == InputResult.PROCESSED) {
+ return true;
+ }
+
+ return super.keyReleased(ch, keyCode, modifiers);
}
@Override
@@ -321,15 +337,6 @@ public class CottonInventoryScreen<T extends SyncedGuiDescription> extends Handl
}
@Override
- public boolean changeFocus(boolean lookForwards) {
- if (description != null) {
- description.cycleFocus(lookForwards);
- }
-
- return true;
- }
-
- @Override
protected void addElementNarrations(NarrationMessageBuilder builder) {
if (description != null) NarrationHelper.addNarrations(description.getRootPanel(), builder);
}
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/impl/FocusHandler.java b/src/main/java/io/github/cottonmc/cotton/gui/impl/FocusHandler.java
deleted file mode 100644
index 61cffa6..0000000
--- a/src/main/java/io/github/cottonmc/cotton/gui/impl/FocusHandler.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package io.github.cottonmc.cotton.gui.impl;
-
-import io.github.cottonmc.cotton.gui.GuiDescription;
-import io.github.cottonmc.cotton.gui.widget.WPanel;
-import io.github.cottonmc.cotton.gui.widget.WWidget;
-
-/**
- * The implementation for focus cycling.
- */
-public final class FocusHandler {
- public static void cycleFocus(GuiDescription host, boolean lookForwards) {
- boolean result;
- WWidget focus = host.getFocus();
- if (focus == null) {
- result = cycleFocus(host, lookForwards, host.getRootPanel(), null);
- } else {
- result = cycleFocus(host, lookForwards, focus, null);
- }
-
- if (!result) {
- // Try again from the beginning
- cycleFocus(host, lookForwards, host.getRootPanel(), null);
- }
- }
-
- private static boolean cycleFocus(GuiDescription host, boolean lookForwards, WWidget widget, WWidget pivot) {
- WWidget next = widget instanceof WPanel
- ? ((WPanel) widget).cycleFocus(lookForwards, pivot)
- : widget.cycleFocus(lookForwards);
-
- if (next != null) {
- host.requestFocus(next);
- return true;
- } else {
- WPanel parent = widget.getParent();
- if (parent != null) {
- return cycleFocus(host, lookForwards, parent, widget);
- }
- }
-
- return false;
- }
-}
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/impl/client/FocusElements.java b/src/main/java/io/github/cottonmc/cotton/gui/impl/client/FocusElements.java
new file mode 100644
index 0000000..fc2fa42
--- /dev/null
+++ b/src/main/java/io/github/cottonmc/cotton/gui/impl/client/FocusElements.java
@@ -0,0 +1,156 @@
+package io.github.cottonmc.cotton.gui.impl.client;
+
+import net.minecraft.client.gui.AbstractParentElement;
+import net.minecraft.client.gui.Element;
+import net.minecraft.client.gui.ScreenRect;
+import net.minecraft.client.gui.navigation.GuiNavigation;
+import net.minecraft.client.gui.navigation.GuiNavigationPath;
+
+import io.github.cottonmc.cotton.gui.widget.WPanel;
+import io.github.cottonmc.cotton.gui.widget.WWidget;
+import io.github.cottonmc.cotton.gui.widget.data.Rect2i;
+import io.github.cottonmc.cotton.gui.widget.focus.Focus;
+import io.github.cottonmc.cotton.gui.widget.focus.FocusHandler;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+public final class FocusElements {
+ public static PanelFocusElement ofPanel(WPanel panel) {
+ PanelFocusElement result = new PanelFocusElement(panel);
+ result.refreshChildren();
+ return result;
+ }
+
+ public static Stream<FocusElement<?>> toElements(WWidget widget) {
+ if (widget instanceof WPanel panel) {
+ return Stream.of(ofPanel(panel));
+ } else {
+ return fromFoci(widget);
+ }
+ }
+
+ private static Stream<FocusElement<?>> fromFoci(WWidget widget) {
+ @Nullable FocusHandler<?> focusHandler = widget.getFocusHandler();
+ if (focusHandler == null) return Stream.empty();
+
+ return focusHandler.foci().map(focus -> new LeafFocusElement(widget, focus));
+ }
+
+ public sealed interface FocusElement<W extends WWidget> extends Element {
+ W widget();
+ }
+
+ private record LeafFocusElement(WWidget widget, Focus<?> focus) implements FocusElement<WWidget> {
+ @SuppressWarnings("unchecked")
+ @Override
+ public void setFocused(boolean focused) {
+ if (focused) {
+ Focus<?> focus = focus();
+
+ if (focus != null) {
+ widget.requestFocus();
+ ((FocusHandler<Object>) widget.getFocusHandler()).setFocused((Focus<Object>) focus);
+ }
+ } else {
+ widget.releaseFocus();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean isFocused() {
+ if (widget.isFocused()) {
+ FocusHandler<Object> focusHandler = (FocusHandler<Object>) widget.getFocusHandler();
+ if (focusHandler != null) {
+ return focusHandler.isFocused((Focus<Object>) focus);
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public ScreenRect getNavigationFocus() {
+ Rect2i area = focus.area();
+ return new ScreenRect(
+ widget.getAbsoluteX() + area.x(),
+ widget.getAbsoluteY() + area.y(),
+ area.width(), area.height()
+ );
+ }
+
+ @Override
+ public @Nullable GuiNavigationPath getNavigationPath(GuiNavigation navigation) {
+ return widget.canFocus() && !isFocused() ? GuiNavigationPath.of(this) : null;
+ }
+ }
+
+ private static final class PanelFocusElement extends AbstractParentElement implements FocusElement<WPanel> {
+ private final List<FocusElement<?>> children = new ArrayList<>();
+ private final WPanel widget;
+ private List<WWidget> childWidgets;
+
+ private PanelFocusElement(WPanel widget) {
+ this.widget = widget;
+ }
+
+ private void refreshChildren() {
+ boolean shouldRefresh = false;
+ if (childWidgets == null) {
+ childWidgets = widget.streamChildren().toList();
+ shouldRefresh = true;
+ } else {
+ List<WWidget> currentChildren = widget.streamChildren().toList();
+ if (!childWidgets.equals(currentChildren)) {
+ childWidgets = currentChildren;
+ shouldRefresh = true;
+ }
+ }
+
+ if (shouldRefresh) {
+ children.clear();
+ fromFoci(widget).forEach(children::add);
+ childWidgets.stream()
+ .flatMap(FocusElements::toElements)
+ .forEach(children::add);
+ refreshFocus();
+ }
+ }
+
+ @Override
+ public List<FocusElement<?>> children() {
+ refreshChildren();
+ return children;
+ }
+
+ @Override
+ public WPanel widget() {
+ return widget;
+ }
+
+ @Override
+ public @Nullable Element getFocused() {
+ refreshFocus();
+ return super.getFocused();
+ }
+
+ public void refreshFocus() {
+ if (children.isEmpty()) return;
+
+ boolean foundFocus = false;
+ for (FocusElement<?> child : children) {
+ if (child instanceof PanelFocusElement panel) {
+ panel.refreshFocus();
+ }
+
+ if (!foundFocus && child.isFocused()) {
+ setFocused(child);
+ foundFocus = true;
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/impl/mixin/client/ScreenAccessor.java b/src/main/java/io/github/cottonmc/cotton/gui/impl/mixin/client/ScreenAccessor.java
new file mode 100644
index 0000000..f2703ed
--- /dev/null
+++ b/src/main/java/io/github/cottonmc/cotton/gui/impl/mixin/client/ScreenAccessor.java
@@ -0,0 +1,15 @@
+package io.github.cottonmc.cotton.gui.impl.mixin.client;
+
+import net.minecraft.client.gui.Element;
+import net.minecraft.client.gui.screen.Screen;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+import java.util.List;
+
+@Mixin(Screen.class)
+public interface ScreenAccessor {
+ @Accessor("children")
+ List<Element> libgui$getChildren();
+}
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/impl/mixin/client/package-info.java b/src/main/java/io/github/cottonmc/cotton/gui/impl/mixin/client/package-info.java
new file mode 100644
index 0000000..ce1472f
--- /dev/null
+++ b/src/main/java/io/github/cottonmc/cotton/gui/impl/mixin/client/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Internal implementation classes.
+ */
+package io.github.cottonmc.cotton.gui.impl.mixin.client;
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/WAbstractSlider.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/WAbstractSlider.java
index 83ad651..945c121 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/widget/WAbstractSlider.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/WAbstractSlider.java
@@ -319,7 +319,7 @@ public abstract class WAbstractSlider extends WWidget {
@Environment(EnvType.CLIENT)
@Override
- public void onKeyPressed(int ch, int key, int modifiers) {
+ public InputResult onKeyPressed(int ch, int key, int modifiers) {
boolean valueChanged = false;
if (modifiers == 0) {
if (isDecreasingKey(ch, direction) && value > min) {
@@ -343,15 +343,20 @@ public abstract class WAbstractSlider extends WWidget {
onValueChanged(value);
pendingDraggingFinishedFromKeyboard = true;
}
+
+ return InputResult.of(valueChanged);
}
@Environment(EnvType.CLIENT)
@Override
- public void onKeyReleased(int ch, int key, int modifiers) {
+ public InputResult onKeyReleased(int ch, int key, int modifiers) {
if (pendingDraggingFinishedFromKeyboard && (isDecreasingKey(ch, direction) || isIncreasingKey(ch, direction))) {
if (draggingFinishedListener != null) draggingFinishedListener.accept(value);
pendingDraggingFinishedFromKeyboard = false;
+ return InputResult.PROCESSED;
}
+
+ return InputResult.IGNORED;
}
/**
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/WButton.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/WButton.java
index 78874b6..de212fb 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/widget/WButton.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/WButton.java
@@ -151,10 +151,13 @@ public class WButton extends WWidget {
@Environment(EnvType.CLIENT)
@Override
- public void onKeyPressed(int ch, int key, int modifiers) {
+ public InputResult onKeyPressed(int ch, int key, int modifiers) {
if (isActivationKey(ch)) {
onClick(0, 0, 0);
+ return InputResult.PROCESSED;
}
+
+ return InputResult.IGNORED;
}
/**
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/WItem.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/WItem.java
index 89b973a..32770c3 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/widget/WItem.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/WItem.java
@@ -64,9 +64,7 @@ public class WItem extends WWidget {
MinecraftClient mc = MinecraftClient.getInstance();
ItemRenderer renderer = mc.getItemRenderer();
- renderer.zOffset = 100f;
- renderer.renderInGui(items.get(current), x + getWidth() / 2 - 8, y + getHeight() / 2 - 8);
- renderer.zOffset = 0f;
+ renderer.renderInGui(matrices, items.get(current), x + getWidth() / 2 - 8, y + getHeight() / 2 - 8);
}
/**
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 a85b08a..ff7548c 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
@@ -21,6 +21,9 @@ import io.github.cottonmc.cotton.gui.impl.LibGuiCommon;
import io.github.cottonmc.cotton.gui.impl.VisualLogger;
import io.github.cottonmc.cotton.gui.impl.client.NarrationMessages;
import io.github.cottonmc.cotton.gui.widget.data.InputResult;
+import io.github.cottonmc.cotton.gui.widget.data.Rect2i;
+import io.github.cottonmc.cotton.gui.widget.focus.Focus;
+import io.github.cottonmc.cotton.gui.widget.focus.FocusHandler;
import io.github.cottonmc.cotton.gui.widget.icon.Icon;
import org.jetbrains.annotations.Nullable;
@@ -30,6 +33,7 @@ import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
+import java.util.stream.Stream;
/**
* A widget that displays an item that can be interacted with.
@@ -92,6 +96,42 @@ public class WItemSlot extends WWidget {
private int hoveredSlot = -1;
private Predicate<ItemStack> filter = ValidatedSlot.DEFAULT_ITEM_FILTER;
private final Set<ChangeListener> listeners = new HashSet<>();
+ private final FocusHandler<Integer> focusHandler = new FocusHandler<>() {
+ @Override
+ public boolean isFocused(Focus<Integer> focus) {
+ return focusedSlot == focus.key();
+ }
+
+ @Override
+ public void setFocused(Focus<Integer> focus) {
+ focusedSlot = focus.key();
+ }
+
+ @Override
+ public Stream<Focus<Integer>> foci() {
+ Stream.Builder<Focus<Integer>> builder = Stream.builder();
+ int index = 0;
+
+ for (int y = 0; y < slotsHigh; y++) {
+ for (int x = 0; x < slotsWide; x++) {
+ int slotX = x * 18;
+ int slotY = y * 18;
+ int size = 18;
+
+ if (big) {
+ slotX -= 4;
+ slotY -= 4;
+ size = 26;
+ }
+
+ builder.add(new Focus<>(index, new Rect2i(slotX, slotY, size, size)));
+ index++;
+ }
+ }
+
+ return builder.build();
+ }
+ };
public WItemSlot(Inventory inventory, int startIndex, int slotsWide, int slotsHigh, boolean big) {
this();
@@ -311,14 +351,17 @@ public class WItemSlot extends WWidget {
@Environment(EnvType.CLIENT)
@Override
- public void onKeyPressed(int ch, int key, int modifiers) {
+ public InputResult onKeyPressed(int ch, int key, int modifiers) {
if (isActivationKey(ch) && host instanceof ScreenHandler && focusedSlot >= 0) {
ScreenHandler handler = (ScreenHandler) host;
MinecraftClient client = MinecraftClient.getInstance();
ValidatedSlot peer = peers.get(focusedSlot);
client.interactionManager.clickSlot(handler.syncId, peer.id, 0, SlotActionType.PICKUP, client.player);
+ return InputResult.PROCESSED;
}
+
+ return InputResult.IGNORED;
}
/**
@@ -396,24 +439,8 @@ public class WItemSlot extends WWidget {
@Nullable
@Override
- public WWidget cycleFocus(boolean lookForwards) {
- if (focusedSlot < 0) {
- focusedSlot = lookForwards ? 0 : (slotsWide * slotsHigh - 1);
- return this;
- }
-
- if (lookForwards) {
- focusedSlot++;
- if (focusedSlot >= slotsWide * slotsHigh) {
- focusedSlot = -1;
- return null;
- } else {
- return this;
- }
- } else {
- focusedSlot--;
- return focusedSlot >= 0 ? this : null;
- }
+ public FocusHandler<?> getFocusHandler() {
+ return focusHandler;
}
@Override
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 a853b24..433ea61 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
@@ -7,7 +7,6 @@ import net.minecraft.client.util.math.MatrixStack;
import io.github.cottonmc.cotton.gui.GuiDescription;
import io.github.cottonmc.cotton.gui.client.BackgroundPainter;
import io.github.cottonmc.cotton.gui.widget.data.Insets;
-import org.jetbrains.annotations.Nullable;
import java.util.AbstractList;
import java.util.ArrayList;
@@ -156,73 +155,6 @@ public abstract class WPanel extends WWidget {
for(WWidget child : children) child.tick();
}
- @Nullable
- @Override
- public WWidget cycleFocus(boolean lookForwards) {
- return cycleFocus(lookForwards, null);
- }
-
- /**
- * Cycles the focus inside this panel.
- *
- * @param lookForwards whether this should cycle forwards (true) or backwards (false)
- * @param pivot the widget that should be cycled around (can be null for beginning / end)
- * @return the next focused widget, or null if should exit to the parent panel
- * @since 2.0.0
- */
- @Nullable
- public WWidget cycleFocus(boolean lookForwards, @Nullable WWidget pivot) {
- if (pivot == null) {
- if (lookForwards) {
- for (WWidget child : children) {
- WWidget result = checkFocusCycling(lookForwards, child);
- if (result != null) return result;
- }
- } else if (!children.isEmpty()) {
- for (int i = children.size() - 1; i >= 0; i--) {
- WWidget child = children.get(i);
- WWidget result = checkFocusCycling(lookForwards, child);
- if (result != null) return result;
- }
- }
- } else {
- int currentIndex = children.indexOf(pivot);
-
- if (currentIndex == -1) { // outside widget
- currentIndex = lookForwards ? 0 : children.size() - 1;
- }
-
- if (lookForwards) {
- if (currentIndex < children.size() - 1) {
- for (int i = currentIndex + 1; i < children.size(); i++) {
- WWidget child = children.get(i);
- WWidget result = checkFocusCycling(lookForwards, child);
- if (result != null) return result;
- }
- }
- } else { // look forwards = false
- if (currentIndex > 0) {
- for (int i = currentIndex - 1; i >= 0; i--) {
- WWidget child = children.get(i);
- WWidget result = checkFocusCycling(lookForwards, child);
- if (result != null) return result;
- }
- }
- }
- }
-
- return null;
- }
-
- @Nullable
- private WWidget checkFocusCycling(boolean lookForwards, WWidget child) {
- if (child.canFocus() || child instanceof WPanel) {
- return child.cycleFocus(lookForwards);
- }
-
- return null;
- }
-
@Override
public void onShown() {
for (WWidget child : children) {
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/WScrollBar.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/WScrollBar.java
index 2882dbd..4e2ddb4 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/widget/WScrollBar.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/WScrollBar.java
@@ -205,7 +205,7 @@ public class WScrollBar extends WWidget {
}
@Override
- public void onKeyPressed(int ch, int key, int modifiers) {
+ public InputResult onKeyPressed(int ch, int key, int modifiers) {
WAbstractSlider.Direction direction = axis == Axis.HORIZONTAL
? WAbstractSlider.Direction.RIGHT
: WAbstractSlider.Direction.DOWN;
@@ -214,11 +214,15 @@ public class WScrollBar extends WWidget {
if (value < getMaxScrollValue()) {
value++;
}
+ return InputResult.PROCESSED;
} else if (WAbstractSlider.isDecreasingKey(ch, direction)) {
if (value > 0) {
value--;
}
+ return InputResult.PROCESSED;
}
+
+ return InputResult.IGNORED;
}
@Environment(EnvType.CLIENT)
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
index dd11eaf..1104929 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/widget/WTabPanel.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/WTabPanel.java
@@ -364,10 +364,13 @@ public class WTabPanel extends WPanel {
@Environment(EnvType.CLIENT)
@Override
- public void onKeyPressed(int ch, int key, int modifiers) {
+ public InputResult onKeyPressed(int ch, int key, int modifiers) {
if (isActivationKey(ch)) {
onClick(0, 0, 0);
+ return InputResult.PROCESSED;
}
+
+ return InputResult.IGNORED;
}
@Environment(EnvType.CLIENT)
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/WTextField.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/WTextField.java
index f09d8f3..3ce7de6 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/widget/WTextField.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/WTextField.java
@@ -252,8 +252,7 @@ public class WTextField extends WWidget {
BufferBuilder buffer = tessellator.getBuffer();
Matrix4f model = matrices.peek().getPositionMatrix();
RenderSystem.setShaderColor(0.0F, 0.0F, 1.0F, 1.0F);
- RenderSystem.setShader(GameRenderer::getPositionTexProgram);
- RenderSystem.disableTexture();
+ RenderSystem.setShader(GameRenderer::getPositionProgram);
RenderSystem.enableColorLogicOp();
RenderSystem.logicOp(GlStateManager.LogicOp.OR_REVERSE);
buffer.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION);
@@ -263,7 +262,6 @@ public class WTextField extends WWidget {
buffer.vertex(model, x, y, 0).next();
BufferRenderer.drawWithGlobalProgram(buffer.end());
RenderSystem.disableColorLogicOp();
- RenderSystem.enableTexture();
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
}
@@ -364,8 +362,9 @@ public class WTextField extends WWidget {
@Environment(EnvType.CLIENT)
@Override
- public void onCharTyped(char ch) {
+ public InputResult onCharTyped(char ch) {
insertText(ch + "");
+ return InputResult.PROCESSED;
}
@Environment(EnvType.CLIENT)
@@ -457,19 +456,19 @@ public class WTextField extends WWidget {
@Environment(EnvType.CLIENT)
@Override
- public void onKeyPressed(int ch, int key, int modifiers) {
- if (!isEditable()) return;
+ public InputResult onKeyPressed(int ch, int key, int modifiers) {
+ if (!isEditable()) return InputResult.IGNORED;
if (Screen.isCopy(ch)) {
copySelection();
- return;
+ return InputResult.PROCESSED;
} else if (Screen.isPaste(ch)) {
paste();
- return;
+ return InputResult.PROCESSED;
} else if (Screen.isSelectAll(ch)) {
select = 0;
cursor = text.length();
- return;
+ return InputResult.PROCESSED;
}
switch (ch) {
@@ -489,8 +488,13 @@ public class WTextField extends WWidget {
}
cursor = text.length();
}
+ default -> {
+ return InputResult.IGNORED;
+ }
}
scrollCursorIntoView();
+
+ return InputResult.PROCESSED;
}
@Override
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/WToggleButton.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/WToggleButton.java
index 1149a9b..47f013e 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/widget/WToggleButton.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/WToggleButton.java
@@ -137,10 +137,13 @@ public class WToggleButton extends WWidget {
}
@Override
- public void onKeyPressed(int ch, int key, int modifiers) {
+ public InputResult onKeyPressed(int ch, int key, int modifiers) {
if (isActivationKey(ch)) {
onClick(0, 0, 0);
+ return InputResult.PROCESSED;
}
+
+ return InputResult.IGNORED;
}
protected void onToggle(boolean on) {
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 f1fc707..25c9767 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
@@ -11,6 +11,7 @@ import io.github.cottonmc.cotton.gui.GuiDescription;
import io.github.cottonmc.cotton.gui.impl.VisualLogger;
import io.github.cottonmc.cotton.gui.widget.data.InputResult;
import io.github.cottonmc.cotton.gui.widget.data.ObservableProperty;
+import io.github.cottonmc.cotton.gui.widget.focus.FocusHandler;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
@@ -237,25 +238,31 @@ public class WWidget {
* Notifies this widget that a character has been typed. This method is subject to key repeat,
* and may be called for characters that do not directly have a corresponding keyboard key.
* @param ch the character typed
+ * @return {@link InputResult#PROCESSED} if the event is handled, {@link InputResult#IGNORED} otherwise.
*/
@Environment(EnvType.CLIENT)
- public void onCharTyped(char ch) {
+ public InputResult onCharTyped(char ch) {
+ return InputResult.IGNORED;
}
/**
* Notifies this widget that a key has been pressed.
* @param key the GLFW scancode of the key
+ * @return {@link InputResult#PROCESSED} if the event is handled, {@link InputResult#IGNORED} otherwise.
*/
@Environment(EnvType.CLIENT)
- public void onKeyPressed(int ch, int key, int modifiers) {
+ public InputResult onKeyPressed(int ch, int key, int modifiers) {
+ return InputResult.IGNORED;
}
/**
* Notifies this widget that a key has been released
* @param key the GLFW scancode of the key
+ * @return {@link InputResult#PROCESSED} if the event is handled, {@link InputResult#IGNORED} otherwise.
*/
@Environment(EnvType.CLIENT)
- public void onKeyReleased(int ch, int key, int modifiers) {
+ public InputResult onKeyReleased(int ch, int key, int modifiers) {
+ return InputResult.IGNORED;
}
/** Notifies this widget that it has gained focus */
@@ -422,17 +429,15 @@ public class WWidget {
public void tick() {}
/**
- * Cycles the focus inside this widget.
- *
- * <p>If this widget is not focusable, returns null.
+ * Returns the focus handler of this widget. The focus
+ * handler provides the focusable areas of this widget,
+ * and applies cycling through them.
*
- * @param lookForwards whether this should cycle forwards (true) or backwards (false)
- * @return the next focused widget, or null if should exit to the parent panel
- * @since 2.0.0
+ * @return the focus handler, or {@code null} if not available
+ * @since 7.0.0
*/
- @Nullable
- public WWidget cycleFocus(boolean lookForwards) {
- return canFocus() ? (isFocused() ? null : this) : null;
+ public @Nullable FocusHandler<?> getFocusHandler() {
+ return canFocus() ? FocusHandler.simple(this) : null;
}
/**
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/data/InputResult.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/data/InputResult.java
index 97e8215..4fed438 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/widget/data/InputResult.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/data/InputResult.java
@@ -2,7 +2,7 @@ package io.github.cottonmc.cotton.gui.widget.data;
/**
* Specifies whether an input event was ignored or processed.
- * Used for mouse input events.
+ * Used for mouse and keyboard input events.
*
* @since 4.0.0
*/
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/data/Rect2i.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/data/Rect2i.java
new file mode 100644
index 0000000..b8e81a3
--- /dev/null
+++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/data/Rect2i.java
@@ -0,0 +1,13 @@
+package io.github.cottonmc.cotton.gui.widget.data;
+
+/**
+ * An immutable, two-dimensional int rectangle consisting of a position and dimensions.
+ * This record can be used to represent rectangles on the screen.
+ *
+ * @param x the X coordinate
+ * @param y the Y coordinate
+ * @param width the horizontal size
+ * @param height the vertical size
+ */
+public record Rect2i(int x, int y, int width, int height) {
+}
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/focus/Focus.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/focus/Focus.java
new file mode 100644
index 0000000..b0b3ab3
--- /dev/null
+++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/focus/Focus.java
@@ -0,0 +1,27 @@
+package io.github.cottonmc.cotton.gui.widget.focus;
+
+import io.github.cottonmc.cotton.gui.widget.data.Rect2i;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * A focus is a focusable area in a widget.
+ *
+ * <p>Foci can also carry a "key", which is a custom data value
+ * used to identify a specific focus. For example, an item slot grid
+ * widget might use {@code K = Integer} to identify each individual slot.
+ *
+ * @param key the key
+ * @param area the focusable area in widget space
+ * @param <K> the key type
+ */
+public record Focus<K>(K key, Rect2i area) {
+ /**
+ * Creates a focus of an area and {@code null} data.
+ *
+ * @param area the area
+ * @return the focus
+ */
+ public static Focus<@Nullable Void> of(Rect2i area) {
+ return new Focus<>(null, area);
+ }
+}
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/focus/FocusHandler.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/focus/FocusHandler.java
new file mode 100644
index 0000000..f7e5e0d
--- /dev/null
+++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/focus/FocusHandler.java
@@ -0,0 +1,50 @@
+package io.github.cottonmc.cotton.gui.widget.focus;
+
+import io.github.cottonmc.cotton.gui.widget.WWidget;
+import io.github.cottonmc.cotton.gui.widget.data.Rect2i;
+
+import java.util.stream.Stream;
+
+/**
+ * Manages the state of individual {@linkplain Focus foci} in a widget.
+ * Each instance should be specific to one widget.
+ *
+ * @param <K> the focus key type
+ */
+public interface FocusHandler<K> {
+ /**
+ * Checks if a focus is focused in the target widget.
+ * If the target widget is not focused itself, none of its foci should have focus.
+ *
+ * @param focus the focus to check
+ * @return {@code true} if the focus is focused, {@code false} otherwise
+ */
+ boolean isFocused(Focus<K> focus);
+
+ /**
+ * Applies a focus to the target widget.
+ *
+ * <p>This method does not need to {@linkplain WWidget#requestFocus request the GUI's focus}
+ * for the widget; that is the responsibility of the caller.
+ *
+ * @param focus the focus
+ */
+ void setFocused(Focus<K> focus);
+
+ /**
+ * {@return a stream of all foci in the target widget}
+ */
+ Stream<Focus<K>> foci();
+
+ /**
+ * Creates a simple focus handler for a focusable widget.
+ * The focus handler provides the whole widget area as its only focus area.
+ *
+ * @param widget the widget
+ * @return the focus handler
+ */
+ static FocusHandler<?> simple(WWidget widget) {
+ Rect2i widgetArea = new Rect2i(0, 0, widget.getWidth(), widget.getHeight());
+ return new SimpleFocusHandler(widget, widgetArea);
+ }
+}
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/focus/SimpleFocusHandler.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/focus/SimpleFocusHandler.java
new file mode 100644
index 0000000..76b0cc1
--- /dev/null
+++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/focus/SimpleFocusHandler.java
@@ -0,0 +1,23 @@
+package io.github.cottonmc.cotton.gui.widget.focus;
+
+import io.github.cottonmc.cotton.gui.widget.WWidget;
+import io.github.cottonmc.cotton.gui.widget.data.Rect2i;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.stream.Stream;
+
+record SimpleFocusHandler(WWidget widget, Rect2i area) implements FocusHandler<@Nullable Void> {
+ @Override
+ public boolean isFocused(Focus<@Nullable Void> focus) {
+ return widget.isFocused();
+ }
+
+ @Override
+ public void setFocused(Focus<@Nullable Void> focusArea) {
+ }
+
+ @Override
+ public Stream<Focus<@Nullable Void>> foci() {
+ return Stream.of(Focus.of(area));
+ }
+}
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/focus/package-info.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/focus/package-info.java
new file mode 100644
index 0000000..dc2c958
--- /dev/null
+++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/focus/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * The widget focus API.
+ */
+package io.github.cottonmc.cotton.gui.widget.focus;
diff --git a/src/main/java/io/github/cottonmc/cotton/gui/widget/icon/ItemIcon.java b/src/main/java/io/github/cottonmc/cotton/gui/widget/icon/ItemIcon.java
index f8455ba..af90b79 100644
--- a/src/main/java/io/github/cottonmc/cotton/gui/widget/icon/ItemIcon.java
+++ b/src/main/java/io/github/cottonmc/cotton/gui/widget/icon/ItemIcon.java
@@ -1,6 +1,5 @@
package io.github.cottonmc.cotton.gui.widget.icon;
-import com.mojang.blaze3d.systems.RenderSystem;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.MinecraftClient;
@@ -46,16 +45,12 @@ public class ItemIcon implements Icon {
// TODO: Make this not ignore the actual matrices
MinecraftClient client = MinecraftClient.getInstance();
ItemRenderer renderer = client.getItemRenderer();
- MatrixStack modelViewMatrices = RenderSystem.getModelViewStack();
-
float scale = size != 16 ? ((float) size / 16f) : 1f;
- modelViewMatrices.push();
- modelViewMatrices.translate(x, y, 0);
- modelViewMatrices.scale(scale, scale, 1);
- RenderSystem.applyModelViewMatrix();
- renderer.renderInGui(stack, 0, 0);
- modelViewMatrices.pop();
- RenderSystem.applyModelViewMatrix();
+ matrices.push();
+ matrices.translate(x, y, 0);
+ matrices.scale(scale, scale, 1);
+ renderer.renderInGui(matrices, stack, 0, 0);
+ matrices.pop();
}
}
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index b5d1caf..f127656 100644
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -22,15 +22,16 @@
"client": ["io.github.cottonmc.cotton.gui.impl.client.LibGuiClient"],
"modmenu": ["io.github.cottonmc.cotton.gui.impl.modmenu.ModMenuSupport"]
},
+ "mixins": ["mixins.libgui.json"],
"depends": {
"java": ">=17",
- "fabricloader": ">=0.14.11",
+ "fabricloader": ">=0.14.17",
"fabric-api-base": ">=0.4.4",
"fabric-lifecycle-events-v1": "^2.0.2",
"fabric-networking-api-v1": "^1.0.21",
- "fabric-rendering-v1": "^1.13.0",
- "minecraft": ">=1.19.3",
- "jankson": "^5.0.0",
+ "fabric-rendering-v1": "^2.1.0",
+ "minecraft": ">=1.19.4",
+ "jankson": "^5.0.1",
"libninepatch": "^1.2.0"
},
"suggests": {
diff --git a/src/main/resources/mixins.libgui.json b/src/main/resources/mixins.libgui.json
new file mode 100644
index 0000000..24cd8ec
--- /dev/null
+++ b/src/main/resources/mixins.libgui.json
@@ -0,0 +1,14 @@
+{
+ "required": true,
+ "minVersion": "0.8",
+ "compatibilityLevel": "JAVA_17",
+ "package": "io.github.cottonmc.cotton.gui.impl.mixin",
+
+ "client": [
+ "client.ScreenAccessor"
+ ],
+
+ "injectors": {
+ "defaultRequire": 1
+ }
+}