From f92c4ad7ee983712d282ac7508e69fae559e2a8f Mon Sep 17 00:00:00 2001 From: Michael Hillcox Date: Mon, 20 Jul 2026 14:38:11 +0100 Subject: [PATCH 1/8] Rework widget scrolling to support x+y scrolling (#198) * feat: adjust the scrolling calls to support both the X+Y scrolling directions part of FTBTeam/FTB-Mods-Issues#2059 * fix: issues with scrolling behaviour + logic * fix: copilot feedback --- build.gradle | 2 +- .../config/ui/EditStringConfigOverlay.java | 7 ++--- .../ui/resource/ResourceSelectorScreen.java | 12 +++++---- .../ftb/mods/ftblibrary/ui/BaseScreen.java | 10 +++---- .../ftb/mods/ftblibrary/ui/DropDownMenu.java | 5 ---- .../ftb/mods/ftblibrary/ui/IntTextBox.java | 8 +++--- .../dev/ftb/mods/ftblibrary/ui/Panel.java | 26 +++++++++++-------- .../ftb/mods/ftblibrary/ui/ScreenWrapper.java | 6 +++-- .../dev/ftb/mods/ftblibrary/ui/ScrollBar.java | 8 +++--- .../dev/ftb/mods/ftblibrary/ui/Widget.java | 12 +++++++-- 10 files changed, 56 insertions(+), 40 deletions(-) diff --git a/build.gradle b/build.gradle index 6bf78fec..6ccf2399 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { id "architectury-plugin" version "3.4-SNAPSHOT" id "dev.architectury.loom" version "1.10-SNAPSHOT" apply false - id "me.modmuss50.mod-publish-plugin" version "2.0.0-beta.2" + id "me.modmuss50.mod-publish-plugin" version "2.1.0" } apply from: 'https://raw.githubusercontent.com/FTBTeam/mods-meta/main/gradle/changelog.gradle' diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/config/ui/EditStringConfigOverlay.java b/common/src/main/java/dev/ftb/mods/ftblibrary/config/ui/EditStringConfigOverlay.java index 944838e7..b1568121 100644 --- a/common/src/main/java/dev/ftb/mods/ftblibrary/config/ui/EditStringConfigOverlay.java +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/config/ui/EditStringConfigOverlay.java @@ -166,12 +166,13 @@ public void onEnterPressed() { } @Override - public boolean mouseScrolled(double scroll) { - return config.scrollValue(currentValue, scroll > 0).map(v -> { + public boolean mouseScrolled(double mouseX, double mouseY, double xDelta, double yDelta) { + var directionlessDelta = xDelta != 0 ? xDelta : yDelta; + return config.scrollValue(currentValue, directionlessDelta > 0).map(v -> { textBox.setText(config.getStringFromValue(v)); textBox.setSelectionPos(textBox.getCursorPos()); return true; - }).orElse(super.mouseScrolled(scroll)); + }).orElse(super.mouseScrolled(mouseX, mouseY, xDelta, yDelta)); } } } diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/config/ui/resource/ResourceSelectorScreen.java b/common/src/main/java/dev/ftb/mods/ftblibrary/config/ui/resource/ResourceSelectorScreen.java index 7ecfcc77..a8566441 100644 --- a/common/src/main/java/dev/ftb/mods/ftblibrary/config/ui/resource/ResourceSelectorScreen.java +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/config/ui/resource/ResourceSelectorScreen.java @@ -1,7 +1,5 @@ package dev.ftb.mods.ftblibrary.config.ui.resource; -import com.google.common.base.Stopwatch; -import com.mojang.datafixers.util.Pair; import dev.ftb.mods.ftblibrary.FTBLibrary; import dev.ftb.mods.ftblibrary.config.ConfigCallback; import dev.ftb.mods.ftblibrary.config.ResourceConfigValue; @@ -21,6 +19,8 @@ import dev.ftb.mods.ftblibrary.ui.misc.SimpleToast; import dev.ftb.mods.ftblibrary.util.SearchTerms; import dev.ftb.mods.ftblibrary.util.TooltipList; +import com.google.common.base.Stopwatch; +import com.mojang.datafixers.util.Pair; import net.minecraft.ChatFormatting; import net.minecraft.Util; import net.minecraft.client.Minecraft; @@ -347,13 +347,15 @@ public CountTextBox() { } @Override - public boolean mouseScrolled(double scroll) { + public boolean mouseScrolled(double mouseX, double mouseY, double xDelta, double yDelta) { if (!isMouseOver) return false; + + var directionlessDelta = xDelta != 0 ? xDelta : yDelta; if (isShiftKeyDown()) { - int adj = scroll > 0 ? getCount() : -getCount() / 2; + int adj = directionlessDelta > 0 ? getCount() : -getCount() / 2; adjust(adj); } else { - adjust((int) Math.signum(scroll)); + adjust((int) Math.signum(directionlessDelta)); } return true; } diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/BaseScreen.java b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/BaseScreen.java index 4ac652f0..e27303fd 100644 --- a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/BaseScreen.java +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/BaseScreen.java @@ -1,7 +1,5 @@ package dev.ftb.mods.ftblibrary.ui; -import com.mojang.blaze3d.platform.InputConstants; -import com.mojang.blaze3d.platform.Window; import dev.ftb.mods.ftblibrary.icon.Color4I; import dev.ftb.mods.ftblibrary.ui.input.Key; import dev.ftb.mods.ftblibrary.ui.input.KeyModifiers; @@ -10,6 +8,8 @@ import dev.ftb.mods.ftblibrary.util.BooleanConsumer; import dev.ftb.mods.ftblibrary.util.TooltipList; import dev.ftb.mods.ftblibrary.util.client.ClientUtils; +import com.mojang.blaze3d.platform.InputConstants; +import com.mojang.blaze3d.platform.Window; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.ChatScreen; @@ -468,11 +468,11 @@ public void mouseReleased(MouseButton button) { } @Override - public boolean mouseScrolled(double scroll) { - if (focusedWidget != null && focusedWidget.mouseScrolled(scroll)) { + public boolean mouseScrolled(double mouseX, double mouseY, double xDelta, double yDelta) { + if (focusedWidget != null && focusedWidget.mouseScrolled(mouseX, mouseY, xDelta, yDelta)) { return true; } - return modalPanels.isEmpty() ? super.mouseScrolled(scroll) : modalPanels.peekFirst().mouseScrolled(scroll); + return modalPanels.isEmpty() ? super.mouseScrolled(mouseX, mouseY, xDelta, yDelta) : modalPanels.peekFirst().mouseScrolled(mouseX, mouseY, xDelta, yDelta); } @Override diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/DropDownMenu.java b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/DropDownMenu.java index c0c3757c..eb3a3ae9 100644 --- a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/DropDownMenu.java +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/DropDownMenu.java @@ -31,11 +31,6 @@ public void onTextChanged() { this.scrollBar = new PanelScrollBar(this, ScrollBar.Plane.VERTICAL, mainPanel); } - @Override - public boolean scrollPanel(double scroll) { - return super.scrollPanel(scroll); - } - @Override public void addWidgets() { add(textBox); diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/IntTextBox.java b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/IntTextBox.java index a744b46f..e40af13f 100644 --- a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/IntTextBox.java +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/IntTextBox.java @@ -43,9 +43,11 @@ public void setMinMax(int min, int max) { } @Override - public boolean mouseScrolled(double scroll) { + public boolean mouseScrolled(double mouseX, double mouseY, double xDelta, double yDelta) { + var directionlessDelta = xDelta != 0 ? xDelta : yDelta; + if (allowInput()) { - setAmount(getIntValue() + (int) scroll); + setAmount(getIntValue() + (int) directionlessDelta); return true; } return false; @@ -69,4 +71,4 @@ public void ensureValue() { setAmount(max); } } -} \ No newline at end of file +} diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/Panel.java b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/Panel.java index 92a42da5..ec941608 100644 --- a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/Panel.java +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/Panel.java @@ -331,19 +331,19 @@ public void mouseReleased(MouseButton button) { } @Override - public boolean mouseScrolled(double scroll) { + public boolean mouseScrolled(double mouseX, double mouseY, double xDelta, double yDelta) { setOffset(true); for (var i = widgets.size() - 1; i >= 0; i--) { var widget = widgets.get(i); - if (widget.isEnabled() && widget.mouseScrolled(scroll)) { + if (widget.isEnabled() && widget.mouseScrolled(mouseX, mouseY, xDelta, yDelta)) { setOffset(false); return true; } } - var scrollPanel = scrollPanel(scroll); + var scrollPanel = scrollPanel(xDelta, yDelta); setOffset(false); return scrollPanel; } @@ -365,15 +365,23 @@ public boolean mouseDragged(int button, double dragX, double dragY) { return false; } - public boolean scrollPanel(double scroll) { + public boolean scrollPanel(double xDelta, double yDelta) { if (attachedScrollbar != null || !isMouseOver()) { return false; } - if (isDefaultScrollVertical() != isShiftKeyDown()) { - return movePanelScroll(0, -getScrollStep() * scroll); + // No scroll direction was given? + var directionlessDelta = yDelta != 0 ? yDelta : xDelta; + if (directionlessDelta == 0) { + return false; + } + + // If the user is pressing shift, we'll always attempt to scroll horizontally, otherwise we'll just blindly apply both directions + if (isShiftKeyDown()) { + var scrollAmount = -getScrollStep() * directionlessDelta; + return movePanelScroll(scrollAmount, 0); } else { - return movePanelScroll(-getScrollStep() * scroll, 0); + return movePanelScroll(-getScrollStep() * xDelta, -getScrollStep() * yDelta); } } @@ -404,10 +412,6 @@ public boolean movePanelScroll(double dx, double dy) { return getScrollX() != sx || getScrollY() != sy; } - public boolean isDefaultScrollVertical() { - return true; - } - public double getScrollStep() { return scrollStep; } diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/ScreenWrapper.java b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/ScreenWrapper.java index 4dae5bb3..62645566 100644 --- a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/ScreenWrapper.java +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/ScreenWrapper.java @@ -1,10 +1,10 @@ package dev.ftb.mods.ftblibrary.ui; -import dev.architectury.platform.Platform; import dev.ftb.mods.ftblibrary.ui.input.Key; import dev.ftb.mods.ftblibrary.ui.input.KeyModifiers; import dev.ftb.mods.ftblibrary.ui.input.MouseButton; import dev.ftb.mods.ftblibrary.util.TooltipList; +import dev.architectury.platform.Platform; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.Screen; import net.minecraft.world.item.ItemStack; @@ -53,7 +53,9 @@ public boolean mouseReleased(double x, double y, int button) { @Override public boolean mouseScrolled(double x, double y, double dirX, double dirY) { - return wrappedGui.mouseScrolled(dirY) || super.mouseScrolled(x, y, dirX, dirY); + return wrappedGui.mouseScrolled(x, y, dirX, dirY) || + wrappedGui.mouseScrolled(dirY) || // TODO: Remove this when all guis are updated to the new mouseScrolled method + super.mouseScrolled(x, y, dirX, dirY); } @Override diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/ScrollBar.java b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/ScrollBar.java index 0947c850..d71669f2 100644 --- a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/ScrollBar.java +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/ScrollBar.java @@ -71,9 +71,11 @@ public boolean mousePressed(MouseButton button) { } @Override - public boolean mouseScrolled(double scroll) { - if (scroll != 0 && canMouseScrollPlane() && canMouseScroll()) { - setValue(getValue() - getScrollStep() * scroll); + public boolean mouseScrolled(double mouseX, double mouseY, double xDelta, double yDelta) { + // We don't care which direction the user scrolled, just apply it. + var scrollDelta = yDelta == 0 ? xDelta : yDelta; + if (scrollDelta != 0 && canMouseScrollPlane() && canMouseScroll()) { + setValue(getValue() - getScrollStep() * scrollDelta); return true; } diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/Widget.java b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/Widget.java index a587aad0..12853849 100644 --- a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/Widget.java +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/Widget.java @@ -1,11 +1,11 @@ package dev.ftb.mods.ftblibrary.ui; -import com.mojang.blaze3d.platform.Window; import dev.ftb.mods.ftblibrary.ui.input.Key; import dev.ftb.mods.ftblibrary.ui.input.KeyModifiers; import dev.ftb.mods.ftblibrary.ui.input.MouseButton; import dev.ftb.mods.ftblibrary.util.TooltipList; import dev.ftb.mods.ftblibrary.util.client.PositionedIngredient; +import com.mojang.blaze3d.platform.Window; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.Screen; @@ -192,10 +192,18 @@ public boolean mouseDoubleClicked(MouseButton button) { public void mouseReleased(MouseButton button) { } - public boolean mouseScrolled(double scroll) { + /** + * @deprecated use {@link #mouseScrolled(double, double, double, double)} instead + */ + @Deprecated + public boolean mouseScrolled(double yDelta) { return false; } + public boolean mouseScrolled(double mouseX, double mouseY, double xDelta, double yDelta) { + return this.mouseScrolled(yDelta); + } + public boolean mouseDragged(int button, double dragX, double dragY) { return false; } From 239bab43f8bc047d2920ad696a77a581bde73b8a Mon Sep 17 00:00:00 2001 From: Michael Hillcox Date: Mon, 20 Jul 2026 14:42:50 +0100 Subject: [PATCH 2/8] feat: backported keymapping + modifiers abstraction support (#201) * feat: backported keymapping + modifiers abstraction support * build: junit build fix --------- Co-authored-by: Des Herriott --- build.gradle | 15 +- .../mods/ftblibrary/ui/MultilineTextBox.java | 5 +- .../dev/ftb/mods/ftblibrary/ui/TextBox.java | 4 +- .../dev/ftb/mods/ftblibrary/ui/input/Key.java | 19 ++- .../ui/misc/KeyReferenceScreen.java | 11 +- .../ftblibrary/util/client/ClientUtils.java | 12 +- .../ftb/mods/ftblibrary/util/input/Input.java | 48 +++++++ .../ftblibrary/util/input/InputHelper.java | 37 +++++ .../ftblibrary/util/input/KeyConflict.java | 7 + .../util/input/KeyMappingConfig.java | 130 ++++++++++++++++++ .../ftblibrary/util/input/KeyModifier.java | 8 ++ fabric/build.gradle | 11 +- .../fabric/keys/AmecsKeyProvider.java | 51 +++++++ .../integration/fabric/keys/KeyProvider.java | 13 ++ .../fabric/keys/VanillaKeyProvider.java | 28 ++++ .../util/client/fabric/ClientUtilsImpl.java | 8 ++ .../ftblibrary/util/fabric/InputImpl.java | 48 +++++++ gradle.properties | 2 + gradle/wrapper/gradle-wrapper.properties | 2 +- neoforge/build.gradle | 2 +- .../util/client/neoforge/ClientUtilsImpl.java | 8 ++ .../ftblibrary/util/neoforge/InputImpl.java | 83 +++++++++++ 22 files changed, 520 insertions(+), 32 deletions(-) create mode 100644 common/src/main/java/dev/ftb/mods/ftblibrary/util/input/Input.java create mode 100644 common/src/main/java/dev/ftb/mods/ftblibrary/util/input/InputHelper.java create mode 100644 common/src/main/java/dev/ftb/mods/ftblibrary/util/input/KeyConflict.java create mode 100644 common/src/main/java/dev/ftb/mods/ftblibrary/util/input/KeyMappingConfig.java create mode 100644 common/src/main/java/dev/ftb/mods/ftblibrary/util/input/KeyModifier.java create mode 100644 fabric/src/main/java/dev/ftb/mods/ftblibrary/integration/fabric/keys/AmecsKeyProvider.java create mode 100644 fabric/src/main/java/dev/ftb/mods/ftblibrary/integration/fabric/keys/KeyProvider.java create mode 100644 fabric/src/main/java/dev/ftb/mods/ftblibrary/integration/fabric/keys/VanillaKeyProvider.java create mode 100644 fabric/src/main/java/dev/ftb/mods/ftblibrary/util/fabric/InputImpl.java create mode 100644 neoforge/src/main/java/dev/ftb/mods/ftblibrary/util/neoforge/InputImpl.java diff --git a/build.gradle b/build.gradle index 6ccf2399..c117fa10 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { - id "architectury-plugin" version "3.4-SNAPSHOT" - id "dev.architectury.loom" version "1.10-SNAPSHOT" apply false - id "me.modmuss50.mod-publish-plugin" version "2.1.0" + id "architectury-plugin" version "3.5-SNAPSHOT" + id "dev.architectury.loom" version "1.17-SNAPSHOT" apply false + id "me.modmuss50.mod-publish-plugin" version "2.1.1" } apply from: 'https://raw.githubusercontent.com/FTBTeam/mods-meta/main/gradle/changelog.gradle' @@ -31,15 +31,17 @@ allprojects { version = project.mod_version group = project.maven_group - archivesBaseName = project.archives_base_name + + base { + archivesName = project.archives_base_name + } // needs to be done AFTER version is set apply from: "https://raw.githubusercontent.com/FTBTeam/mods-meta/main/gradle/publishing.gradle" - sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = 21 - compileJava { options.encoding = "UTF-8" + options.release = 21 } repositories { @@ -82,6 +84,7 @@ allprojects { dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.11.0-M1' + testImplementation 'org.junit.platform:junit-platform-launcher:1.14.3' } java { diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/MultilineTextBox.java b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/MultilineTextBox.java index 7539b3d2..4be1db9c 100644 --- a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/MultilineTextBox.java +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/MultilineTextBox.java @@ -1,13 +1,12 @@ package dev.ftb.mods.ftblibrary.ui; -import com.mojang.blaze3d.platform.GlStateManager; -import com.mojang.blaze3d.platform.InputConstants; -import com.mojang.blaze3d.systems.RenderSystem; import dev.ftb.mods.ftblibrary.core.mixin.common.MultilineTextFieldAccess; import dev.ftb.mods.ftblibrary.icon.Color4I; import dev.ftb.mods.ftblibrary.ui.input.Key; import dev.ftb.mods.ftblibrary.ui.input.KeyModifiers; import dev.ftb.mods.ftblibrary.ui.input.MouseButton; +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.MultilineTextField; diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/TextBox.java b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/TextBox.java index bec831c2..451e5dcd 100644 --- a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/TextBox.java +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/TextBox.java @@ -1,12 +1,12 @@ package dev.ftb.mods.ftblibrary.ui; -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.PoseStack; import dev.ftb.mods.ftblibrary.icon.Color4I; import dev.ftb.mods.ftblibrary.icon.Icon; import dev.ftb.mods.ftblibrary.ui.input.Key; import dev.ftb.mods.ftblibrary.ui.input.KeyModifiers; import dev.ftb.mods.ftblibrary.ui.input.MouseButton; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.ChatFormatting; import net.minecraft.Util; import net.minecraft.client.gui.Font; diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/input/Key.java b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/input/Key.java index d715f3ab..35b737e1 100644 --- a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/input/Key.java +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/input/Key.java @@ -1,25 +1,24 @@ package dev.ftb.mods.ftblibrary.ui.input; +import dev.ftb.mods.ftblibrary.util.client.ClientUtils; import com.mojang.blaze3d.platform.InputConstants; -import dev.architectury.injectables.annotations.ExpectPlatform; import net.minecraft.client.KeyMapping; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.Screen; import org.lwjgl.glfw.GLFW; - public class Key { public final int keyCode, scanCode; public final KeyModifiers modifiers; - public Key(int k, int s, int m) { - keyCode = k; - scanCode = s; - modifiers = new KeyModifiers(m); + + public Key(int keyCode, int scanCode, int modifiers) { + this.keyCode = keyCode; + this.scanCode = scanCode; + this.modifiers = new KeyModifiers(modifiers); } - @ExpectPlatform - private static boolean matchesWithoutConflicts(KeyMapping keyBinding, InputConstants.Key keyCode) { - throw new AssertionError(); + public boolean matches(KeyMapping keyMapping) { + return ClientUtils.input().matches(keyMapping, keyCode, scanCode); } public boolean is(int k) { @@ -35,7 +34,7 @@ public boolean esc() { } public boolean escOrInventory() { - return esc() || matchesWithoutConflicts(Minecraft.getInstance().options.keyInventory, getInputMapping()); + return esc() || matches(Minecraft.getInstance().options.keyInventory); } public boolean enter() { diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/misc/KeyReferenceScreen.java b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/misc/KeyReferenceScreen.java index 0b617726..8b4a0498 100644 --- a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/misc/KeyReferenceScreen.java +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/misc/KeyReferenceScreen.java @@ -20,12 +20,13 @@ public class KeyReferenceScreen extends BaseScreen { private final Panel textPanel; private final PanelScrollBar scrollBar; private final SimpleTextButton closeButton; - private final String[] translationKeys; public KeyReferenceScreen(String... translationKeys) { - this.translationKeys = translationKeys; + this(buildText(translationKeys)); + } - textPanel = new TextPanel(this); + public KeyReferenceScreen(List> keyValueComponents) { + textPanel = new TextPanel(this, keyValueComponents); closeButton = new SimpleTextButton(this, Component.translatable("gui.close"), Icons.CANCEL) { @Override @@ -167,10 +168,10 @@ public void draw(GuiGraphics graphics, Theme theme, int x, int y, int w, int h) private class TextPanel extends Panel { private final TwoColumnList textWidget; - public TextPanel(Panel panel) { + public TextPanel(Panel panel, List> keyValueComponents) { super(panel); - textWidget = new TwoColumnList(this, buildText(translationKeys)); + textWidget = new TwoColumnList(this, keyValueComponents); } @Override diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/util/client/ClientUtils.java b/common/src/main/java/dev/ftb/mods/ftblibrary/util/client/ClientUtils.java index 6be09a9f..dc650913 100644 --- a/common/src/main/java/dev/ftb/mods/ftblibrary/util/client/ClientUtils.java +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/util/client/ClientUtils.java @@ -1,12 +1,13 @@ package dev.ftb.mods.ftblibrary.util.client; +import dev.ftb.mods.ftblibrary.FTBLibrary; +import dev.ftb.mods.ftblibrary.ui.CustomClickEvent; +import dev.ftb.mods.ftblibrary.ui.IScreenWrapper; +import dev.ftb.mods.ftblibrary.util.input.Input; import dev.architectury.event.EventResult; import dev.architectury.event.events.client.ClientChatEvent; import dev.architectury.fluid.FluidStack; import dev.architectury.injectables.annotations.ExpectPlatform; -import dev.ftb.mods.ftblibrary.FTBLibrary; -import dev.ftb.mods.ftblibrary.ui.CustomClickEvent; -import dev.ftb.mods.ftblibrary.ui.IScreenWrapper; import net.minecraft.ResourceLocationException; import net.minecraft.Util; import net.minecraft.client.Minecraft; @@ -29,6 +30,11 @@ public class ClientUtils { private static final MethodType EMPTY_METHOD_TYPE = MethodType.methodType(void.class); private static final HashMap> staticMethodCache = new HashMap<>(); + @ExpectPlatform + public static Input input() { + throw new AssertionError(); + } + public static void execClientCommand(String command, boolean printChat) { if (!command.isEmpty() && Minecraft.getInstance().player != null) { EventResult res = ClientChatEvent.SEND.invoker().send(command, null); diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/util/input/Input.java b/common/src/main/java/dev/ftb/mods/ftblibrary/util/input/Input.java new file mode 100644 index 00000000..e5b0935d --- /dev/null +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/util/input/Input.java @@ -0,0 +1,48 @@ +package dev.ftb.mods.ftblibrary.util.input; + +import net.minecraft.client.KeyMapping; +import net.minecraft.network.chat.Component; + +public interface Input { + /** + * Register one or more keymappings with vanilla. + * + * **IMPORTANT**: all keymappings for any single keymapping category _must_ be registered in a single call to this + * method, or you will get a "Category is already registered" exception from vanilla. It is fine to call this method + * multiple times, as long as the same category isn't passed to more than one invocation of this method. + * + * @param modId your mod's unique ID + * @param keyMappings one or more keymapping objects + * @throws IllegalArgumentException if the method is called with no keymappings + */ + void registerKeyMapping(String modId, KeyMapping... keyMappings); + + /** + * Create a `KeyMapping` object in a platform-independent fashion. The returned object may be some subclass + * or extension of the vanilla `KeyMapping` object, but can be used as one. + * + * @param config defines the setup for the keymapping object + * @return a new keymapping object + */ + KeyMapping createKeyMapping(KeyMappingConfig config); + + /** + * Get a displayable string for the key and possible modifiers for a {@code KeyMapping} object + * @param keyMapping the keymapping object + */ + Component getKeyMappingDisplayName(KeyMapping keyMapping); + + /** + * Check if the key event matches the given key mapping. This differs from [KeyMapping#matches(KeyEvent)] + * in that modifiers, if supported in the current environment, are also checked. + * + * @param mapping the KeyMapping to check + * @param keyCode the key code of the event + * @param scanCode the scan code of the event + * @return true if there's a match + * + * @implNote modifiers are supported if the platform is NeoForge, or if the platform is Fabric with the Amecs + * mod installed + */ + boolean matches(KeyMapping mapping, int keyCode, int scanCode); +} diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/util/input/InputHelper.java b/common/src/main/java/dev/ftb/mods/ftblibrary/util/input/InputHelper.java new file mode 100644 index 00000000..6bcd8d1a --- /dev/null +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/util/input/InputHelper.java @@ -0,0 +1,37 @@ +package dev.ftb.mods.ftblibrary.util.input; + +import dev.ftb.mods.ftblibrary.util.client.ClientUtils; +import com.mojang.blaze3d.platform.InputConstants; +import net.minecraft.client.KeyMapping; + +public class InputHelper { + /** + * Register a keymapping with no default binding, in a platform-independent way. Convenience wrapper for + * [Input#createKeyMapping(KeyMappingConfig)]. + * + * @param name the keymap name + * @param category the keymap category + * @return the keymapping object + */ + public static KeyMapping createSimpleKeyMapping(String name, String category) { + return createSimpleKeyMapping(name, category, InputConstants.UNKNOWN.getValue()); + } + + /** + * Register a keymapping a default binding and zero or more modifiers, in a platform-independent way. Convenience + * wrapper for [Input#createKeyMapping(KeyMappingConfig)]. + * + * @param name the keymap name + * @param category the keymap category + * @return the keymapping object + * @implNote Although multiple modifiers are accepted, NeoForge keymapping objects only support a single modifier. So + * it is not recommended to pass more than one modifier. + */ + public static KeyMapping createSimpleKeyMapping(String name, String category, int keyCode, KeyModifier... modifiers) { + KeyMappingConfig.Builder builder = KeyMappingConfig.builder(name, category).keyboard(keyCode); + for (var m : modifiers) { + builder.modifier(m); + } + return ClientUtils.input().createKeyMapping(builder.build()); + } +} diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/util/input/KeyConflict.java b/common/src/main/java/dev/ftb/mods/ftblibrary/util/input/KeyConflict.java new file mode 100644 index 00000000..79ef1969 --- /dev/null +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/util/input/KeyConflict.java @@ -0,0 +1,7 @@ +package dev.ftb.mods.ftblibrary.util.input; + +public enum KeyConflict { + EVERYWHERE, + ANY_GUI, + IN_GAME +} diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/util/input/KeyMappingConfig.java b/common/src/main/java/dev/ftb/mods/ftblibrary/util/input/KeyMappingConfig.java new file mode 100644 index 00000000..69e0aa66 --- /dev/null +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/util/input/KeyMappingConfig.java @@ -0,0 +1,130 @@ +package dev.ftb.mods.ftblibrary.util.input; + +import com.mojang.blaze3d.platform.InputConstants; +import org.jetbrains.annotations.Nullable; + +public record KeyMappingConfig( + String id, + String category, + TypedKey key, + KeyModifier modifier, + @Nullable TypedKey noModifierFallbackKey, + KeyConflict conflictContext +) { + public String translationKey() { + return "key." + category + "." + id; + } + + public InputConstants.Type type(boolean supportsModifiers) { + var selectedKey = !supportsModifiers && noModifierFallbackKey != null ? noModifierFallbackKey : key; + return selectedKey.type(); + } + + public int code(boolean supportsModifiers) { + var selectedKey = !supportsModifiers && noModifierFallbackKey != null ? noModifierFallbackKey : key; + return selectedKey.code(); + } + + public static Builder builder(String id, String category) { + return new Builder(id, category); + } + + public static class Builder { + private final String id; + private final String category; + + // We only support one modifier even though the Fabric library supports multiple because NeoForge's KeyModifier only supports one + // When on Vanilla / Fabric without a mod, this is completely ignored anyway. + private KeyModifier modifier = KeyModifier.NONE; + + private TypedKey key = TypedKey.fromKey(InputConstants.UNKNOWN); + + // If there is no modifier support on the current platform, this key will override the main key + // as there may be a case where a key with modifier makes sense but without the modifier, it doesn't and another key is preferred. + private @Nullable TypedKey noModifierFallbackKey = null; + + // This is ignored on Fabric even with the mod as this is a NeoForge concept. + private KeyConflict conflictContext = KeyConflict.EVERYWHERE; + + private Builder(String id, String category) { + this.id = id; + this.category = category; + } + + public Builder shift() { + return modifier(KeyModifier.SHIFT); + } + + public Builder control() { + return modifier(KeyModifier.CONTROL); + } + + public Builder alt() { + return modifier(KeyModifier.ALT); + } + + public Builder modifier(KeyModifier modifier) { + this.modifier = modifier; + return this; + } + + public Builder key(InputConstants.Type type, int code) { + this.key = new TypedKey(type, code); + return this; + } + + public Builder key(InputConstants.Key key) { + this.key = TypedKey.fromKey(key); + return this; + } + + public Builder keyboard(int keyCode) { + return key(InputConstants.Type.KEYSYM.getOrCreate(keyCode)); + } + + public Builder mouse(int buttonCode) { + return key(InputConstants.Type.MOUSE.getOrCreate(buttonCode)); + } + + public Builder scan(int buttonCode) { + return key(InputConstants.Type.SCANCODE.getOrCreate(buttonCode)); + } + + public Builder noModifierFallbackKey(InputConstants.Key noModifierFallbackKey) { + this.noModifierFallbackKey = TypedKey.fromKey(noModifierFallbackKey); + return this; + } + + public Builder noModifierFallbackKey(InputConstants.Type type, int code) { + this.noModifierFallbackKey = new TypedKey(type, code); + return this; + } + + public Builder noModifierFallbackKeyboard(int keyCode) { + return noModifierFallbackKey(InputConstants.Type.KEYSYM.getOrCreate(keyCode)); + } + + public Builder noModifierFallbackMouse(int buttonCode) { + return noModifierFallbackKey(InputConstants.Type.MOUSE.getOrCreate(buttonCode)); + } + + public Builder noModifierFallbackScan(int buttonCode) { + return noModifierFallbackKey(InputConstants.Type.SCANCODE.getOrCreate(buttonCode)); + } + + public Builder conflictContext(KeyConflict conflictContext) { + this.conflictContext = conflictContext; + return this; + } + + public KeyMappingConfig build() { + return new KeyMappingConfig(id, category, key, modifier, noModifierFallbackKey, conflictContext); + } + } + + public record TypedKey(InputConstants.Type type, int code) { + public static TypedKey fromKey(InputConstants.Key key) { + return new TypedKey(key.getType(), key.getValue()); + } + } +} diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/util/input/KeyModifier.java b/common/src/main/java/dev/ftb/mods/ftblibrary/util/input/KeyModifier.java new file mode 100644 index 00000000..3e2d059f --- /dev/null +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/util/input/KeyModifier.java @@ -0,0 +1,8 @@ +package dev.ftb.mods.ftblibrary.util.input; + +public enum KeyModifier { + NONE, + SHIFT, + CONTROL, + ALT, +} diff --git a/fabric/build.gradle b/fabric/build.gradle index 0d78cdc2..d5d2da3a 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -1,5 +1,5 @@ plugins { - id "com.github.johnrengelman.shadow" version "7.0.0" + id "com.gradleup.shadow" version "9.5.0" } architectury { @@ -35,6 +35,12 @@ artifacts { dev(jar) } +repositories { + maven { + url = uri("https://maven.siphalor.de/") + } +} + dependencies { modApi "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}" modApi "net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}" @@ -45,6 +51,9 @@ dependencies { common(project(path: ":common", configuration: "dev")) { transitive false } shadowCommon(project(path: ":common", configuration: "transformProductionFabric")) { transitive false } + + modCompileOnlyApi "de.siphalor.amecs.amecs-key-modifiers:amecs-key-modifiers-${amecs_key_modifiers_version}" + modLocalRuntime "de.siphalor.amecs.amecs-bundle:amecs-bundle-${amecs_key_bundle_version}" } apply from: "https://raw.githubusercontent.com/FTBTeam/mods-meta/main/gradle/extra-local-mods.gradle" diff --git a/fabric/src/main/java/dev/ftb/mods/ftblibrary/integration/fabric/keys/AmecsKeyProvider.java b/fabric/src/main/java/dev/ftb/mods/ftblibrary/integration/fabric/keys/AmecsKeyProvider.java new file mode 100644 index 00000000..1b3ac257 --- /dev/null +++ b/fabric/src/main/java/dev/ftb/mods/ftblibrary/integration/fabric/keys/AmecsKeyProvider.java @@ -0,0 +1,51 @@ +package dev.ftb.mods.ftblibrary.integration.fabric.keys; + + +import dev.ftb.mods.ftblibrary.util.input.KeyMappingConfig; +import dev.ftb.mods.ftblibrary.util.input.KeyModifier; +import de.siphalor.amecs.key_modifiers.api.AmecsKeyMappingWithKeyModifiers; +import de.siphalor.amecs.key_modifiers.api.AmecsKeyModifierCombination; +import net.minecraft.client.KeyMapping; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +public class AmecsKeyProvider implements KeyProvider { + @Override + public KeyMapping create(KeyMappingConfig config) { + return new AmecsKeyMappingWithKeyModifiers( + config.translationKey(), + config.type(true), + config.code(true), + config.category(), + fromModifier(config.modifier()) + ); + } + + @Override + public Component getKeyMappingDisplayName(KeyMapping keyMapping) { + return keyMapping.getTranslatedKeyMessage(); + } + + @Override + public boolean matchModifier(KeyMapping mapping) { + var modifiers = asAmecsMapping(mapping).getDefaultAmecsKeyModifiers(); + return (Screen.hasAltDown() || !modifiers.getAlt()) + && (Screen.hasControlDown() || !modifiers.getControl()) + && (Screen.hasShiftDown() || !modifiers.getShift()); + } + + private AmecsKeyMappingWithKeyModifiers asAmecsMapping(KeyMapping keyMapping) { + if (keyMapping instanceof AmecsKeyMappingWithKeyModifiers amecs) { + return amecs; + } + throw new IllegalStateException("using AmecsKeyProvider but keymapping is " + keyMapping.getClass()); + } + + private AmecsKeyModifierCombination fromModifier(KeyModifier modifier) { + return new AmecsKeyModifierCombination( + modifier == KeyModifier.ALT, + modifier == KeyModifier.CONTROL, + modifier == KeyModifier.SHIFT + ); + } +} diff --git a/fabric/src/main/java/dev/ftb/mods/ftblibrary/integration/fabric/keys/KeyProvider.java b/fabric/src/main/java/dev/ftb/mods/ftblibrary/integration/fabric/keys/KeyProvider.java new file mode 100644 index 00000000..d85ed8ea --- /dev/null +++ b/fabric/src/main/java/dev/ftb/mods/ftblibrary/integration/fabric/keys/KeyProvider.java @@ -0,0 +1,13 @@ +package dev.ftb.mods.ftblibrary.integration.fabric.keys; + +import dev.ftb.mods.ftblibrary.util.input.KeyMappingConfig; +import net.minecraft.client.KeyMapping; +import net.minecraft.network.chat.Component; + +public interface KeyProvider { + KeyMapping create(KeyMappingConfig config); + + Component getKeyMappingDisplayName(KeyMapping keyMapping); + + boolean matchModifier(KeyMapping mapping); +} diff --git a/fabric/src/main/java/dev/ftb/mods/ftblibrary/integration/fabric/keys/VanillaKeyProvider.java b/fabric/src/main/java/dev/ftb/mods/ftblibrary/integration/fabric/keys/VanillaKeyProvider.java new file mode 100644 index 00000000..cc355c36 --- /dev/null +++ b/fabric/src/main/java/dev/ftb/mods/ftblibrary/integration/fabric/keys/VanillaKeyProvider.java @@ -0,0 +1,28 @@ +package dev.ftb.mods.ftblibrary.integration.fabric.keys; + +import dev.ftb.mods.ftblibrary.util.input.KeyMappingConfig; +import net.minecraft.client.KeyMapping; +import net.minecraft.network.chat.Component; + +public class VanillaKeyProvider implements KeyProvider { + @Override + public KeyMapping create(KeyMappingConfig config) { + return new KeyMapping( + config.translationKey(), + config.type(false), + config.code(false), + config.category() + ); + } + + @Override + public Component getKeyMappingDisplayName(KeyMapping keyMapping) { + return keyMapping.getTranslatedKeyMessage(); + } + + @Override + public boolean matchModifier(KeyMapping mapping) { + // no modifiers supported by vanilla + return true; + } +} diff --git a/fabric/src/main/java/dev/ftb/mods/ftblibrary/util/client/fabric/ClientUtilsImpl.java b/fabric/src/main/java/dev/ftb/mods/ftblibrary/util/client/fabric/ClientUtilsImpl.java index 736b0b3d..4146c1e3 100644 --- a/fabric/src/main/java/dev/ftb/mods/ftblibrary/util/client/fabric/ClientUtilsImpl.java +++ b/fabric/src/main/java/dev/ftb/mods/ftblibrary/util/client/fabric/ClientUtilsImpl.java @@ -1,5 +1,7 @@ package dev.ftb.mods.ftblibrary.util.client.fabric; +import dev.ftb.mods.ftblibrary.util.fabric.InputImpl; +import dev.ftb.mods.ftblibrary.util.input.Input; import dev.architectury.fluid.FluidStack; import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandlerRegistry; import net.minecraft.client.Minecraft; @@ -7,6 +9,12 @@ import net.minecraft.resources.ResourceLocation; public class ClientUtilsImpl { + private static final Input fabricInputImpl = new InputImpl(); + + public static Input input() { + return fabricInputImpl; + } + public static ResourceLocation getStillTexture(FluidStack stack) { var fluid = stack.getFluid(); var handler = FluidRenderHandlerRegistry.INSTANCE.get(fluid); diff --git a/fabric/src/main/java/dev/ftb/mods/ftblibrary/util/fabric/InputImpl.java b/fabric/src/main/java/dev/ftb/mods/ftblibrary/util/fabric/InputImpl.java new file mode 100644 index 00000000..ed77631a --- /dev/null +++ b/fabric/src/main/java/dev/ftb/mods/ftblibrary/util/fabric/InputImpl.java @@ -0,0 +1,48 @@ +package dev.ftb.mods.ftblibrary.util.fabric; + +import dev.ftb.mods.ftblibrary.integration.fabric.keys.AmecsKeyProvider; +import dev.ftb.mods.ftblibrary.integration.fabric.keys.KeyProvider; +import dev.ftb.mods.ftblibrary.integration.fabric.keys.VanillaKeyProvider; +import dev.ftb.mods.ftblibrary.util.Lazy; +import dev.ftb.mods.ftblibrary.util.input.Input; +import dev.ftb.mods.ftblibrary.util.input.KeyMappingConfig; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.KeyMapping; +import net.minecraft.network.chat.Component; +import org.apache.commons.lang3.Validate; + +public class InputImpl implements Input { + private final Lazy keyProvider = Lazy.of(InputImpl::initKeyProvider); + + private static KeyProvider initKeyProvider() { + if (FabricLoader.getInstance().isModLoaded("amecs")) { + return new AmecsKeyProvider(); + } else { + return new VanillaKeyProvider(); + } + } + + @Override + public void registerKeyMapping(String modId, KeyMapping... keyMappings) { + Validate.isTrue(keyMappings.length > 0, "must provide at least one keymapping"); + for (var k : keyMappings) { + KeyBindingHelper.registerKeyBinding(k); + } + } + + @Override + public KeyMapping createKeyMapping(KeyMappingConfig config) { + return keyProvider.get().create(config); + } + + @Override + public Component getKeyMappingDisplayName(KeyMapping keyMapping) { + return keyProvider.get().getKeyMappingDisplayName(keyMapping); + } + + @Override + public boolean matches(KeyMapping mapping, int keyCode, int scanCode) { + return mapping.matches(keyCode, scanCode) && keyProvider.get().matchModifier(mapping); + } +} diff --git a/gradle.properties b/gradle.properties index 798e28ac..87d19865 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,6 +23,8 @@ architectury_version=13.0.6 rei_version=16.0.754 jei_version=19.8.5.118 emi_version=1.1.12 +amecs_key_modifiers_version=mc1.21.1:1.0.2 +amecs_key_bundle_version=mc1.21.1:1.6.3 # mod_menu_version=1.14.6+ curseforge_id_forge=404465 curseforge_id_fabric=438495 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d6e308a6..52ad5e71 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/neoforge/build.gradle b/neoforge/build.gradle index 04cd9ced..2f9f6bfb 100644 --- a/neoforge/build.gradle +++ b/neoforge/build.gradle @@ -1,5 +1,5 @@ plugins { - id "com.github.johnrengelman.shadow" version "7.0.0" + id "com.gradleup.shadow" version "9.5.0" } architectury { diff --git a/neoforge/src/main/java/dev/ftb/mods/ftblibrary/util/client/neoforge/ClientUtilsImpl.java b/neoforge/src/main/java/dev/ftb/mods/ftblibrary/util/client/neoforge/ClientUtilsImpl.java index ae6e7c8c..edd0c413 100644 --- a/neoforge/src/main/java/dev/ftb/mods/ftblibrary/util/client/neoforge/ClientUtilsImpl.java +++ b/neoforge/src/main/java/dev/ftb/mods/ftblibrary/util/client/neoforge/ClientUtilsImpl.java @@ -1,5 +1,7 @@ package dev.ftb.mods.ftblibrary.util.client.neoforge; +import dev.ftb.mods.ftblibrary.util.input.Input; +import dev.ftb.mods.ftblibrary.util.neoforge.InputImpl; import dev.architectury.fluid.FluidStack; import dev.architectury.hooks.fluid.FluidStackHooks; import net.minecraft.client.renderer.texture.TextureAtlasSprite; @@ -7,6 +9,12 @@ import net.minecraft.resources.ResourceLocation; public class ClientUtilsImpl { + private static final Input neoInputImpl = new InputImpl(); + + public static Input input() { + return neoInputImpl; + } + public static ResourceLocation getStillTexture(FluidStack stack) { var neoforgeStack = new net.neoforged.neoforge.fluids.FluidStack(Holder.direct(stack.getFluid()), (int) stack.getAmount(), stack.getPatch()); TextureAtlasSprite stillTexture = FluidStackHooks.getStillTexture(neoforgeStack.getFluid()); diff --git a/neoforge/src/main/java/dev/ftb/mods/ftblibrary/util/neoforge/InputImpl.java b/neoforge/src/main/java/dev/ftb/mods/ftblibrary/util/neoforge/InputImpl.java new file mode 100644 index 00000000..8826bd50 --- /dev/null +++ b/neoforge/src/main/java/dev/ftb/mods/ftblibrary/util/neoforge/InputImpl.java @@ -0,0 +1,83 @@ +package dev.ftb.mods.ftblibrary.util.neoforge; + +import dev.ftb.mods.ftblibrary.util.input.Input; +import dev.ftb.mods.ftblibrary.util.input.KeyConflict; +import dev.ftb.mods.ftblibrary.util.input.KeyMappingConfig; +import dev.ftb.mods.ftblibrary.util.input.KeyModifier; +import net.minecraft.client.KeyMapping; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.ModList; +import net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent; +import net.neoforged.neoforge.client.settings.IKeyConflictContext; +import net.neoforged.neoforge.client.settings.KeyConflictContext; +import org.apache.commons.lang3.Validate; + +public class InputImpl implements Input { + @Override + public void registerKeyMapping(String modId, KeyMapping... keyMappings) { + Validate.isTrue(keyMappings.length > 0, "must provide at least one keymapping"); + + getModBusOrThrow(modId).addListener(RegisterKeyMappingsEvent.class, event -> { + for (var k : keyMappings) { + event.register(k); + } + }); + } + + @Override + public KeyMapping createKeyMapping(KeyMappingConfig config) { + return new KeyMapping( + config.translationKey(), + convertConflictContext(config.conflictContext()), + fromModifier(config.modifier()), + config.type(true), + config.code(true), + config.category() + ); + } + + @Override + public Component getKeyMappingDisplayName(KeyMapping keyMapping) { + return keyMapping.getKeyModifier().getCombinedName(keyMapping.getKey(), () -> keyMapping.getKey().getDisplayName()); + } + + @Override + public boolean matches(KeyMapping mapping, int keyCode, int scanCode) { + return mapping.matches(keyCode, scanCode) && matchModifier(mapping); + } + + private boolean matchModifier(KeyMapping mapping) { + return switch (mapping.getKeyModifier()) { + case CONTROL -> Screen.hasControlDown(); + case SHIFT -> Screen.hasShiftDown(); + case ALT -> Screen.hasAltDown(); + case NONE -> true; + }; + } + + private net.neoforged.neoforge.client.settings.KeyModifier fromModifier(KeyModifier modifier) { + return switch (modifier) { + case ALT -> net.neoforged.neoforge.client.settings.KeyModifier.ALT; + case SHIFT -> net.neoforged.neoforge.client.settings.KeyModifier.SHIFT; + case CONTROL -> net.neoforged.neoforge.client.settings.KeyModifier.CONTROL; + case NONE -> net.neoforged.neoforge.client.settings.KeyModifier.NONE; + }; + } + + private IKeyConflictContext convertConflictContext(KeyConflict conflict) { + return switch (conflict) { + case IN_GAME -> KeyConflictContext.IN_GAME; + case ANY_GUI -> KeyConflictContext.GUI; + case EVERYWHERE -> KeyConflictContext.UNIVERSAL; + }; + } + + static IEventBus getModBusOrThrow(String modId) { + return ModList.get().getModContainerById(modId) + .map(ModContainer::getEventBus) + .orElseThrow(); + } +} From 3757f2d0dd046799e17ff923d082a3d6c6738885 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Mon, 20 Jul 2026 14:46:47 +0100 Subject: [PATCH 3/8] build: version -> 2101.1.34 --- CHANGELOG.md | 9 +++++++++ gradle.properties | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91b09fac..5d9fcde7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2101.1.34] + +### Added +* Backport keymapping abstraction system from 26.1 + * Support for key modifiers on both NeoForge and Fabric (Fabric support requires the Amecs mod) + +### Changed +* Reworked panel scrolling to properly support vanilla's multi-axis scrolling (e.g. simultaneous X/Y scrolling via trackpad) + ## [2101.1.33] ### Changed diff --git a/gradle.properties b/gradle.properties index 87d19865..bef70bf3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.daemon=false # Mod mod_id=ftblibrary readable_name=FTB Library -mod_version=2101.1.33 +mod_version=2101.1.34 mod_author=FTB Team # Maven archives_base_name=ftb-library From 2ff5aa1d3d0afc83e32482d7e135e2087920c65b Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Mon, 20 Jul 2026 16:13:39 +0100 Subject: [PATCH 4/8] feat: more dynamic "can edit" testing for config editor Added EditableConfigValue#setCanEdit variant which takes BooleanSupplier Better graying-out of uneditable config entries --- .../ftb/mods/ftblibrary/config/ConfigValue.java | 11 +++++++++-- .../ftblibrary/config/ui/EditConfigScreen.java | 15 ++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/config/ConfigValue.java b/common/src/main/java/dev/ftb/mods/ftblibrary/config/ConfigValue.java index 74275089..ec6ec190 100644 --- a/common/src/main/java/dev/ftb/mods/ftblibrary/config/ConfigValue.java +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/config/ConfigValue.java @@ -5,6 +5,7 @@ import dev.ftb.mods.ftblibrary.icon.Icons; import dev.ftb.mods.ftblibrary.ui.Widget; import dev.ftb.mods.ftblibrary.ui.input.MouseButton; +import dev.ftb.mods.ftblibrary.util.BooleanConsumer; import dev.ftb.mods.ftblibrary.util.TooltipList; import net.minecraft.ChatFormatting; import net.minecraft.client.resources.language.I18n; @@ -12,6 +13,7 @@ import org.jetbrains.annotations.Nullable; import java.util.Objects; +import java.util.function.BooleanSupplier; import java.util.function.Consumer; public abstract class ConfigValue implements Comparable> { @@ -24,7 +26,7 @@ public abstract class ConfigValue implements Comparable> { private int order = 0; private String nameKey = ""; private Icon icon = Icons.SETTINGS; - private boolean canEdit = true; + private BooleanSupplier canEdit = () -> true; protected static Component info(String key) { return Component.literal(key + ":").withStyle(ChatFormatting.AQUA); @@ -153,10 +155,15 @@ public ConfigValue setOrder(int o) { } public boolean getCanEdit() { - return canEdit; + return canEdit.getAsBoolean(); } public ConfigValue setCanEdit(boolean e) { + canEdit = () -> e; + return this; + } + + public ConfigValue setCanEdit(BooleanSupplier e) { canEdit = e; return this; } diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/config/ui/EditConfigScreen.java b/common/src/main/java/dev/ftb/mods/ftblibrary/config/ui/EditConfigScreen.java index 04ed9053..9dcaf08f 100644 --- a/common/src/main/java/dev/ftb/mods/ftblibrary/config/ui/EditConfigScreen.java +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/config/ui/EditConfigScreen.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.function.Supplier; import static dev.ftb.mods.ftblibrary.util.TextComponentUtils.hotkeyTooltip; @@ -110,7 +111,7 @@ public boolean onInit() { allConfigButtons.forEach(w -> { if (w instanceof ConfigEntryButton eb) { - widestKey = Math.max(widestKey, getTheme().getFont().width(eb.keyText)); + widestKey = Math.max(widestKey, getTheme().getFont().width(eb.keyText.get())); widestValue = Math.max(widestValue, getTheme().getFont().width(eb.getValueStr())); } else if (w instanceof ConfigGroupButton gb) { widestGroup.setValue(Math.max(widestGroup.intValue(), getTheme().getStringWidth(gb.title))); @@ -265,7 +266,7 @@ public void onClicked(MouseButton button) { private class ConfigEntryButton extends Button implements EditStringConfigOverlay.PosProvider { private final ConfigGroupButton groupButton; private final ConfigValue configValue; - private final Component keyText; + private final Supplier keyText; public ConfigEntryButton(Panel panel, ConfigGroupButton groupButton, ConfigValue configValue) { super(panel); @@ -273,9 +274,9 @@ public ConfigEntryButton(Panel panel, ConfigGroupButton groupButton, ConfigValue this.groupButton = groupButton; this.configValue = configValue; - keyText = this.configValue.getCanEdit() ? + keyText = () -> this.configValue.getCanEdit() ? Component.literal(this.configValue.getName()) : - Component.literal(this.configValue.getName()).withStyle(ChatFormatting.GRAY); + Component.literal(this.configValue.getName()).withStyle(ChatFormatting.DARK_GRAY); } @Override @@ -283,7 +284,7 @@ public void draw(GuiGraphics graphics, Theme theme, int x, int y, int w, int h) Icons.COLOR_BLANK.withColor(Color4I.GRAY).draw(graphics, x, y + 1, 10, 10); Icons.INFO.draw(graphics, x + 1, y + 2, 8, 8); - theme.drawString(graphics, keyText, x + 13, y + 2, Bits.setFlag(0, Theme.SHADOW, isMouseOver())); + theme.drawString(graphics, keyText.get(), x + 13, y + 2, Bits.setFlag(0, Theme.SHADOW, isMouseOver())); Component valueText = configValue.getStringForGUI(configValue.getValue()); @@ -293,7 +294,7 @@ public void draw(GuiGraphics graphics, Theme theme, int x, int y, int w, int h) } var textCol = configValue.getColor().mutable(); - textCol.setAlpha(255); + textCol.setAlpha(configValue.getCanEdit() ? 255 : 128); if (isMouseOver()) { textCol.addBrightness(60); @@ -321,7 +322,7 @@ public void addMouseOverText(TooltipList list) { if (getMouseY() > 18) { int x = getMouseX() - getX(); if (x < 16) { - list.add(keyText.copy().withStyle(ChatFormatting.UNDERLINE)); + list.add(keyText.get().copy().withStyle(ChatFormatting.UNDERLINE)); var tooltip = configValue.getTooltip(); if (!tooltip.isEmpty()) { From c69aa11356d4efee10636dc9b873646860db52c6 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Tue, 21 Jul 2026 16:34:31 +0100 Subject: [PATCH 5/8] fix: ensure multiline textbox doesn't scroll out of bounds Another fix for issues with adding whitespace to the end of a line that's already full https://github.com/FTBTeam/FTB-Mods-Issues/issues/1330 --- .../main/java/dev/ftb/mods/ftblibrary/ui/MultilineTextBox.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/MultilineTextBox.java b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/MultilineTextBox.java index 4be1db9c..dfc9ae88 100644 --- a/common/src/main/java/dev/ftb/mods/ftblibrary/ui/MultilineTextBox.java +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/ui/MultilineTextBox.java @@ -321,6 +321,8 @@ private void scrollToCursor() { } } + d0 = Mth.clamp(d0, 0, Math.max(0, parent.getContentHeight() - parent.height)); + parent.setScrollY(d0); } From 88440533981c214d67cc0bf86d90637c217aeb82 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Wed, 22 Jul 2026 09:09:30 +0100 Subject: [PATCH 6/8] chore: changelog updated --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d9fcde7..3262c588 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added * Backport keymapping abstraction system from 26.1 * Support for key modifiers on both NeoForge and Fabric (Fabric support requires the Amecs mod) +* Added `ConfigValue#setCanEdit` variant which takes a `BooleanSupplier` + * Allows more for dynamic updating of config editor screen ### Changed * Reworked panel scrolling to properly support vanilla's multi-axis scrolling (e.g. simultaneous X/Y scrolling via trackpad) +### Fixed +* Fixed a bug in MultilineTextBox causing widget to blank under certain circumstances (used by FTB Quests quest description editor) + ## [2101.1.33] ### Changed From c4e56322ecee3bff54affa4cafb030d50d39f670 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Wed, 22 Jul 2026 15:31:05 +0100 Subject: [PATCH 7/8] fix: proper atomicity in SNBT#write --- .../java/dev/ftb/mods/ftblibrary/snbt/SNBT.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/snbt/SNBT.java b/common/src/main/java/dev/ftb/mods/ftblibrary/snbt/SNBT.java index 6ad1bd05..e7baba85 100644 --- a/common/src/main/java/dev/ftb/mods/ftblibrary/snbt/SNBT.java +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/snbt/SNBT.java @@ -7,8 +7,10 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.nio.file.AtomicMoveNotSupportedException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.Collection; import java.util.List; @@ -72,11 +74,20 @@ public static List writeLines(CompoundTag nbt) { public static boolean write(Path path, CompoundTag nbt) { try { - if (Files.notExists(path.getParent())) { - Files.createDirectories(path.getParent()); + Path parent = path.getParent(); + if (Files.notExists(parent)) { + Files.createDirectories(parent); } - Files.write(path, writeLines(nbt)); + // write to a temp file first and rename into place, so a crash/kill mid-write + // can never leave a truncated/corrupt file at the real path + Path tmp = parent.resolve(path.getFileName().toString() + ".tmp"); + Files.write(tmp, writeLines(nbt)); + try { + Files.move(tmp, path, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + } catch (AtomicMoveNotSupportedException ex) { + Files.move(tmp, path, StandardCopyOption.REPLACE_EXISTING); + } return true; } catch (Exception ex) { return false; From 467d0098620e6f50baff7a259ea9be0ed2872353 Mon Sep 17 00:00:00 2001 From: Des Herriott Date: Thu, 23 Jul 2026 16:04:56 +0100 Subject: [PATCH 8/8] fix: a couple of extra checks for ConfigValue#getCanEdit --- .../config/ui/EditConfigListScreen.java | 22 ++++++++++--------- .../config/ui/EditConfigScreen.java | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/config/ui/EditConfigListScreen.java b/common/src/main/java/dev/ftb/mods/ftblibrary/config/ui/EditConfigListScreen.java index c11e805d..56a59370 100644 --- a/common/src/main/java/dev/ftb/mods/ftblibrary/config/ui/EditConfigListScreen.java +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/config/ui/EditConfigListScreen.java @@ -154,17 +154,19 @@ public void addMouseOverText(TooltipList list) { @Override public void onClicked(MouseButton button) { - playClickSound(); - CV listType = listConfig.getType(); - listType.setValue(listType.getDefaultValue() == null ? null : listType.copy(listType.getDefaultValue())); - listType.onClicked(this, button, accepted -> { - if (accepted) { - localValues.add(listType.getValue()); - changed = true; - } + if (listConfig.getCanEdit()) { + playClickSound(); + CV listType = listConfig.getType(); + listType.setValue(listType.getDefaultValue() == null ? null : listType.copy(listType.getDefaultValue())); + listType.onClicked(this, button, accepted -> { + if (accepted) { + localValues.add(listType.getValue()); + changed = true; + } - openGui(); - }); + openGui(); + }); + } } @Override diff --git a/common/src/main/java/dev/ftb/mods/ftblibrary/config/ui/EditConfigScreen.java b/common/src/main/java/dev/ftb/mods/ftblibrary/config/ui/EditConfigScreen.java index 9dcaf08f..a809ec89 100644 --- a/common/src/main/java/dev/ftb/mods/ftblibrary/config/ui/EditConfigScreen.java +++ b/common/src/main/java/dev/ftb/mods/ftblibrary/config/ui/EditConfigScreen.java @@ -308,7 +308,7 @@ public void draw(GuiGraphics graphics, Theme theme, int x, int y, int w, int h) @Override public void onClicked(MouseButton button) { - if (!readOnly && getMouseY() >= 20) { + if (!readOnly && getMouseY() >= 20 && configValue.getCanEdit()) { playClickSound(); configValue.onClicked(this, button, accepted -> { if (accepted) changed = true;