diff options
author | Juuz <6596629+Juuxel@users.noreply.github.com> | 2023-03-18 20:07:12 +0200 |
---|---|---|
committer | Juuz <6596629+Juuxel@users.noreply.github.com> | 2023-03-18 20:07:12 +0200 |
commit | 6835bb564b28075dcf05605560827d92994f44d9 (patch) | |
tree | 15579df3daf6fb02ec5b85848731028920fb2785 | |
parent | 8912cc45001f7c580da2a90218a47a8ada80aa70 (diff) | |
download | LibGui-6835bb564b28075dcf05605560827d92994f44d9.tar.gz LibGui-6835bb564b28075dcf05605560827d92994f44d9.tar.bz2 LibGui-6835bb564b28075dcf05605560827d92994f44d9.zip |
Update to 1.19.4, add new focus API
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 + } +} |