aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/dev/isxander
diff options
context:
space:
mode:
authorCrendgrim <Crendgrim@users.noreply.github.com>2024-04-14 20:35:24 +0200
committerGitHub <noreply@github.com>2024-04-14 19:35:24 +0100
commitbb0690af2536edf9ff920c0c229de8b919215850 (patch)
tree4569fb3e674bf6d48c8108cb05e0427d077e4eae /src/main/java/dev/isxander
parent34b62af39b4c9a64e5c04ab8fdf5a978c1cde310 (diff)
downloadYetAnotherConfigLib-bb0690af2536edf9ff920c0c229de8b919215850.tar.gz
YetAnotherConfigLib-bb0690af2536edf9ff920c0c229de8b919215850.tar.bz2
YetAnotherConfigLib-bb0690af2536edf9ff920c0c229de8b919215850.zip
Make dropdown controllers use the new popup widget technology (#162)
* Make dropdown controllers use the new popup screen facility This finally allows for controlling the dropdown with a mouse! :D Scrolling, selecting, and accepting are all working! * Fix mouse hovering of dropdown controllers --------- Co-authored-by: isXander <xander@isxander.dev>
Diffstat (limited to 'src/main/java/dev/isxander')
-rw-r--r--src/main/java/dev/isxander/yacl3/gui/controllers/PopupControllerScreen.java8
-rw-r--r--src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/AbstractDropdownControllerElement.java183
-rw-r--r--src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/DropdownWidget.java233
-rw-r--r--src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/ItemControllerElement.java10
4 files changed, 324 insertions, 110 deletions
diff --git a/src/main/java/dev/isxander/yacl3/gui/controllers/PopupControllerScreen.java b/src/main/java/dev/isxander/yacl3/gui/controllers/PopupControllerScreen.java
index f6a5db3..bb860f0 100644
--- a/src/main/java/dev/isxander/yacl3/gui/controllers/PopupControllerScreen.java
+++ b/src/main/java/dev/isxander/yacl3/gui/controllers/PopupControllerScreen.java
@@ -47,11 +47,19 @@ public class PopupControllerScreen extends Screen {
@Override
public boolean mouseScrolled(double mouseX, double mouseY, /*? if >1.20.1 {*/ double scrollX, /*?}*/ double scrollY) {
+ if (controllerPopup.mouseScrolled(mouseX, mouseY, /*? if >1.20.1 {*/ scrollX, /*?}*/ scrollY)) {
+ return true;
+ }
backgroundYaclScreen.mouseScrolled(mouseX, mouseY, /*? if >1.20.1 {*/ scrollX, /*?}*/ scrollY); //mouseX & mouseY are needed here
return super.mouseScrolled(mouseX, mouseY, /*? if >1.20.1 {*/ scrollX, /*?}*/ scrollY);
}
@Override
+ public void mouseMoved(double mouseX, double mouseY) {
+ controllerPopup.mouseMoved(mouseX, mouseY);
+ }
+
+ @Override
public boolean charTyped(char codePoint, int modifiers) {
return controllerPopup.charTyped(codePoint, modifiers);
}
diff --git a/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/AbstractDropdownControllerElement.java b/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/AbstractDropdownControllerElement.java
index 49e0c0e..7e73332 100644
--- a/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/AbstractDropdownControllerElement.java
+++ b/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/AbstractDropdownControllerElement.java
@@ -1,7 +1,6 @@
package dev.isxander.yacl3.gui.controllers.dropdown;
import com.mojang.blaze3d.platform.InputConstants;
-import com.mojang.blaze3d.vertex.PoseStack;
import dev.isxander.yacl3.api.utils.Dimension;
import dev.isxander.yacl3.gui.YACLScreen;
import dev.isxander.yacl3.gui.controllers.string.StringControllerElement;
@@ -11,18 +10,16 @@ import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
-import java.awt.Color;
import java.util.List;
import java.util.function.Consumer;
public abstract class AbstractDropdownControllerElement<T, U> extends StringControllerElement {
- public static final int MAX_SHOWN_NUMBER_OF_ITEMS = 7;
private final AbstractDropdownController<T> dropdownController;
+ protected DropdownWidget<T> dropdownWidget;
+
protected boolean dropdownVisible = false;
- // Stores the current selection position. The item at this position in the dropdown list will be chosen as the
- // accepted value when the element is closed.
- protected int selectedIndex = 0;
+
// Stores a cached list of matching values
protected List<U> matchingValues = null;
@@ -32,26 +29,24 @@ public abstract class AbstractDropdownControllerElement<T, U> extends StringCont
this.dropdownController.option.addListener((opt, val) -> this.matchingValues = this.computeMatchingValues());
}
- public void showDropdown() {
- dropdownVisible = true;
- selectedIndex = 0;
- }
-
- public void closeDropdown() {
- dropdownVisible = false;
- ensureValidValue();
- }
-
public void ensureValidValue() {
- inputField = dropdownController.getValidValue(inputField, selectedIndex);
- this.matchingValues = this.computeMatchingValues();
+ if (!dropdownController.isValueValid(inputField)) {
+ if (dropdownWidget == null) {
+ inputField = dropdownController.getValidValue(inputField);
+ } else {
+ inputField = dropdownController.getValidValue(inputField, dropdownWidget.selectedIndex());
+ dropdownWidget.resetSelectedIndex();
+ }
+ caretPos = getDefaultCaretPos();
+ this.matchingValues = this.computeMatchingValues();
+ }
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
if (super.mouseClicked(mouseX, mouseY, button)) {
if (!dropdownVisible) {
- showDropdown();
+ createDropdownWidget();
doSelectAll();
}
return true;
@@ -69,7 +64,9 @@ public abstract class AbstractDropdownControllerElement<T, U> extends StringCont
@Override
public void unfocus() {
- closeDropdown();
+ if (dropdownVisible) {
+ removeDropdownWidget();
+ }
super.unfocus();
}
@@ -80,25 +77,25 @@ public abstract class AbstractDropdownControllerElement<T, U> extends StringCont
if (dropdownVisible) {
switch (keyCode) {
case InputConstants.KEY_DOWN -> {
- selectNextEntry();
+ dropdownWidget.selectNextEntry();
return true;
}
case InputConstants.KEY_UP -> {
- selectPreviousEntry();
+ dropdownWidget.selectPreviousEntry();
return true;
}
case InputConstants.KEY_TAB -> {
if (Screen.hasShiftDown()) {
- selectPreviousEntry();
+ dropdownWidget.selectPreviousEntry();
} else {
- selectNextEntry();
+ dropdownWidget.selectNextEntry();
}
return true;
}
}
} else {
if (keyCode == InputConstants.KEY_RETURN || keyCode == InputConstants.KEY_NUMPADENTER) {
- showDropdown();
+ createDropdownWidget();
return true;
}
}
@@ -108,7 +105,7 @@ public abstract class AbstractDropdownControllerElement<T, U> extends StringCont
@Override
public boolean charTyped(char chr, int modifiers) {
if (!dropdownVisible) {
- showDropdown();
+ createDropdownWidget();
}
return super.charTyped(chr, modifiers);
}
@@ -123,26 +120,6 @@ public abstract class AbstractDropdownControllerElement<T, U> extends StringCont
return super.getValueColor();
}
- public void selectNextEntry() {
- if (selectedIndex == getDropdownLength() - 1) {
- selectedIndex = 0;
- } else {
- selectedIndex++;
- }
- }
-
- public void selectPreviousEntry() {
- if (selectedIndex == 0) {
- selectedIndex = getDropdownLength() - 1;
- } else {
- selectedIndex--;
- }
- }
-
- public int getDropdownLength() {
- return matchingValues.size();
- }
-
@Override
public boolean modifyInput(Consumer<StringBuilder> builder) {
boolean success = super.modifyInput(builder);
@@ -161,61 +138,46 @@ public abstract class AbstractDropdownControllerElement<T, U> extends StringCont
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) {
if (matchingValues == null) matchingValues = computeMatchingValues();
-
- super.render(graphics, mouseX, mouseY, delta);
- if (inputFieldFocused && dropdownVisible) {
- PoseStack matrices = graphics.pose();
- matrices.pushPose();
- matrices.translate(0, 0, 200);
- renderDropdown(graphics);
- matrices.popPose();
- }
- }
-
- public void renderDropdown(GuiGraphics graphics) {
- if (matchingValues.isEmpty()) return;
- // Limit the visible options to allow scrolling through the suggestion list
- int begin = Math.max(0, selectedIndex - MAX_SHOWN_NUMBER_OF_ITEMS / 2);
- int end = begin + MAX_SHOWN_NUMBER_OF_ITEMS;
- if (end >= matchingValues.size()) {
- end = matchingValues.size();
- begin = Math.max(0, end - MAX_SHOWN_NUMBER_OF_ITEMS);
- }
-
- renderDropdownBackground(graphics, end - begin);
- if (!matchingValues.isEmpty()) {
- // Highlight the currently selected element
- graphics.setColor(0.0f, 0.0f, 0.0f, 0.5f);
- int x = getDimension().x();
- int y = getDimension().yLimit() + 2 + getDimension().height() * (selectedIndex - begin);
- graphics.fill(x, y, x + getDimension().width(), y + getDimension().height(), -1);
- graphics.setColor(1.0f, 1.0f, 1.0f, 1.0f);
- graphics.renderOutline(x, y, getDimension().width(), getDimension().height(), -1);
-
- }
-
- int n = 1;
- for (int i = begin; i < end; ++i) {
- renderDropdownEntry(graphics, matchingValues.get(i), n);
- ++n;
- }
+ super.render(graphics, mouseX, mouseY, delta);
}
- protected int getDropdownEntryPadding() {
- return 0;
+ void renderDropdownEntry(GuiGraphics graphics, Dimension<Integer> entryDimension, int index) {
+ renderDropdownEntry(graphics, entryDimension, matchingValues.get(index));
}
-
- protected void renderDropdownEntry(GuiGraphics graphics, U value, int n) {
+ protected void renderDropdownEntry(GuiGraphics graphics, Dimension<Integer> entryDimension, U value) {
String entry = getString(value);
- int color = -1;
Component text;
if (entry.isBlank()) {
text = Component.translatable("yacl.control.text.blank").withStyle(ChatFormatting.GRAY);
} else {
text = shortenString(entry);
}
- graphics.drawString(textRenderer, text, getDimension().xLimit() - textRenderer.width(text) - getDecorationPadding() - getDropdownEntryPadding(), getTextY() + n * getDimension().height() + 2, color, true);
+ graphics.drawString(
+ textRenderer,
+ text,
+ entryDimension.xLimit() - textRenderer.width(text) - getDropdownEntryPadding(),
+ getTextY(entryDimension),
+ -1,
+ true
+ );
+ }
+
+ protected int getTextY(Dimension<Integer> dim) {
+ return (int)(dim.y() + dim.height() / 2f - textRenderer.lineHeight / 2f);
+ }
+
+ @Override
+ public void setDimension(Dimension<Integer> dim) {
+ super.setDimension(dim);
+
+ if (dropdownWidget != null) {
+ dropdownWidget.setDimension(dropdownWidget.getDimension().withY(this.getDimension().y()));
+ // checks if the popup is being partially rendered offscreen
+ if (this.getDimension().y() < screen.tabArea.top() || this.getDimension().yLimit() > screen.tabArea.bottom()) {
+ removeDropdownWidget();
+ }
+ }
}
public abstract String getString(U object);
@@ -224,25 +186,32 @@ public abstract class AbstractDropdownControllerElement<T, U> extends StringCont
return Component.literal(GuiUtils.shortenString(value, textRenderer, getDimension().width() - 20, "..."));
}
- public void renderDropdownBackground(GuiGraphics graphics, int numberOfItems) {
- graphics.setColor(0.25f, 0.25f, 0.25f, 1.0f);
- graphics.blit(
- /*? if >1.20.4 {*//*
- Screen.MENU_BACKGROUND,
- *//*?} else {*/
- Screen.BACKGROUND_LOCATION,
- /*?}*/
- getDimension().x(), getDimension().yLimit() + 2, 0,
- 0.0f, 0.0f,
- getDimension().width(), getDimension().height() * numberOfItems + 2,
- 32, 32
- );
- graphics.setColor(1.0f, 1.0f, 1.0f, 1.0f);
- graphics.renderOutline(getDimension().x(), getDimension().yLimit() + 2, getDimension().width(), getDimension().height() * numberOfItems, -1);
- }
-
protected int getDecorationPadding() {
return super.getXPadding();
}
+ protected int getDropdownEntryPadding() {
+ return 0;
+ }
+
+ public void createDropdownWidget() {
+ dropdownVisible = true;
+ dropdownWidget = new DropdownWidget<>(dropdownController, screen, getDimension(), this);
+ screen.addPopupControllerWidget(dropdownWidget);
+ }
+
+ public DropdownWidget<T> dropdownWidget() {
+ return dropdownWidget;
+ }
+
+ public boolean isDropdownVisible() {
+ return dropdownVisible;
+ }
+
+ public void removeDropdownWidget() {
+ ensureValidValue();
+ screen.clearPopupControllerWidget();
+ this.dropdownVisible = false;
+ this.dropdownWidget = null;
+ }
}
diff --git a/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/DropdownWidget.java b/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/DropdownWidget.java
new file mode 100644
index 0000000..f16a1dd
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/DropdownWidget.java
@@ -0,0 +1,233 @@
+package dev.isxander.yacl3.gui.controllers.dropdown;
+
+import com.mojang.blaze3d.vertex.PoseStack;
+import dev.isxander.yacl3.api.utils.Dimension;
+import dev.isxander.yacl3.api.utils.MutableDimension;
+import dev.isxander.yacl3.gui.YACLScreen;
+import dev.isxander.yacl3.gui.controllers.ControllerPopupWidget;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.network.chat.Component;
+
+public class DropdownWidget<T> extends ControllerPopupWidget<AbstractDropdownController<T>> {
+ public static final int MAX_SHOWN_NUMBER_OF_ITEMS = 7;
+ public static final int DROPDOWN_PADDING = 2;
+
+ private final AbstractDropdownControllerElement<T, ?> dropdownElement;
+
+ protected Dimension<Integer> dropdownDim;
+
+ // Scroll offset in the list of possible values
+ protected int firstVisibleIndex = 0;
+ // Stores the current selection position. The item at this position in the dropdown list will be chosen as the
+ // accepted value when the element is closed.
+ protected int selectedIndex = 0;
+
+ public DropdownWidget(AbstractDropdownController<T> control, YACLScreen screen, Dimension<Integer> dim, AbstractDropdownControllerElement<T, ?> dropdownElement) {
+ super(control, screen, dim, dropdownElement);
+ this.dropdownElement = dropdownElement;
+
+ setDimension(dim);
+ }
+
+ @Override
+ public void setDimension(Dimension<Integer> dim) {
+ super.setDimension(dim);
+
+ // Set up the dropdown above the controller ...
+ int dropdownHeight = dim.height() * numberOfVisibleItems();
+ int dropdownY = dim.y() - dropdownHeight - DROPDOWN_PADDING;
+
+ // ... unless it doesn't fit, then place it below
+ if (dropdownY < screen.tabArea.top()) {
+ dropdownY = dim.yLimit() + DROPDOWN_PADDING;
+ }
+
+ dropdownDim = Dimension.ofInt(dim.x(), dropdownY, dim.width(), dropdownHeight);
+ }
+
+ public int entryHeight() {
+ return dropdownElement.getDimension().height();
+ }
+
+ @Override
+ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) {
+ if (dropdownLength() == 0) return;
+
+ PoseStack matrices = graphics.pose();
+ matrices.pushPose();
+ matrices.translate(0, 0, 200);
+
+ // Background
+ graphics.setColor(0.25f, 0.25f, 0.25f, 1.0f);
+ graphics.blit(
+ /*? if >1.20.4 {*//*
+ Screen.MENU_BACKGROUND,
+ *//*?} else {*/
+ Screen.BACKGROUND_LOCATION,
+ /*?}*/
+ dropdownDim.x(), dropdownDim.y(), 0,
+ 0.0f, 0.0f,
+ dropdownDim.width(), dropdownDim.height(),
+ 32, 32
+ );
+ graphics.setColor(1.0f, 1.0f, 1.0f, 1.0f);
+ graphics.renderOutline(dropdownDim.x(), dropdownDim.y(), dropdownDim.width(), dropdownDim.height(), -1);
+
+ // Highlight the currently selected element
+ graphics.setColor(0.0f, 0.0f, 0.0f, 0.5f);
+ int y = dropdownDim.y() + 2 + entryHeight() * selectedVisibleIndex();
+ graphics.fill(dropdownDim.x(), y, dropdownDim.xLimit(), y + entryHeight(), -1);
+ graphics.setColor(1.0f, 1.0f, 1.0f, 1.0f);
+ graphics.renderOutline(dropdownDim.x(), y, dropdownDim.width(), entryHeight(), -1);
+
+ // Render all visible elements
+ MutableDimension<Integer> entryDimension = Dimension.ofInt(
+ dropdownDim.x() - dropdownElement.getDecorationPadding(),
+ dropdownDim.y() + 2,
+ dropdownDim.width(),
+ entryHeight()
+ );
+ for (int i = firstVisibleIndex; i < lastVisibleIndex(); ++i) {
+ dropdownElement.renderDropdownEntry(graphics, entryDimension, i);
+ entryDimension.move(0, entryHeight());
+ }
+
+ matrices.popPose();
+ }
+
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY, int button) {
+ if (isMouseOver(mouseX, mouseY)) {
+ // Closes and cleans up the dropdown
+ dropdownElement.unfocus();
+ return true;
+ } else if (dropdownElement.isMouseOver(mouseX, mouseY)) {
+ return dropdownElement.mouseClicked(mouseX, mouseY, button);
+ } else {
+ close();
+ return false;
+ }
+ }
+
+ @Override
+ public boolean mouseScrolled(double mouseX, double mouseY, /*? if >1.20.1 {*/ double scrollX, /*?}*/ double scrollY) {
+ if (isMouseOver(mouseX, mouseY)) {
+ if (scrollY < 0) {
+ scrollDown();
+ } else {
+ scrollUp();
+ }
+ return true;
+ }
+ return super.mouseScrolled(mouseX, mouseY, /*? if >1.20.1 {*/ scrollX, /*?}*/ scrollY);
+ }
+
+ @Override
+ public void mouseMoved(double mouseX, double mouseY) {
+ if (isMouseOver(mouseX, mouseY)) {
+ int index = (int) ((mouseY - dropdownDim.y()) / entryHeight());
+ selectVisibleItem(index);
+ }
+ }
+
+ @Override
+ public boolean isMouseOver(double mouseX, double mouseY) {
+ return dropdownDim.isPointInside((int) mouseX, (int) mouseY);
+ }
+
+ @Override
+ public boolean charTyped(char chr, int modifiers) {
+ // Done to allow for typing whilst the color picker is visible
+ return dropdownElement.charTyped(chr, modifiers);
+ }
+
+ public int dropdownLength() {
+ return dropdownElement.matchingValues.size();
+ }
+
+ public int numberOfVisibleItems() {
+ return Math.min(MAX_SHOWN_NUMBER_OF_ITEMS, dropdownLength());
+ }
+
+ public int lastVisibleIndex() {
+ return Math.min(firstVisibleIndex + MAX_SHOWN_NUMBER_OF_ITEMS, dropdownLength());
+ }
+
+ public int selectedIndex() {
+ return selectedIndex;
+ }
+ public void resetSelectedIndex() {
+ selectedIndex = 0;
+ }
+ /**
+ * @return the offset of the selected element relative to the first visible entry
+ */
+ public int selectedVisibleIndex() {
+ return selectedIndex - firstVisibleIndex;
+ }
+
+ public void selectVisibleItem(int visibleIndex) {
+ selectedIndex = Math.min(firstVisibleIndex + visibleIndex, dropdownLength() - 1);
+ }
+
+ public void selectNextEntry() {
+ if (selectedIndex == dropdownLength() - 1) {
+ selectedIndex = 0;
+ } else {
+ selectedIndex++;
+ }
+ if (selectedIndex - firstVisibleIndex >= MAX_SHOWN_NUMBER_OF_ITEMS / 2) {
+ centerOnSelectedItem();
+ }
+ }
+
+ public void selectPreviousEntry() {
+ if (selectedIndex == 0) {
+ selectedIndex = dropdownLength() - 1;
+ } else {
+ selectedIndex--;
+ }
+ if (selectedIndex - firstVisibleIndex <= MAX_SHOWN_NUMBER_OF_ITEMS / 2) {
+ centerOnSelectedItem();
+ }
+ }
+
+ private void centerOnSelectedItem() {
+ // Limit the visible options to allow scrolling through the suggestion list
+ int begin = Math.max(0, selectedIndex - MAX_SHOWN_NUMBER_OF_ITEMS / 2);
+ int end = begin + MAX_SHOWN_NUMBER_OF_ITEMS;
+ if (end >= dropdownLength()) {
+ end = dropdownLength();
+ begin = Math.max(0, end - MAX_SHOWN_NUMBER_OF_ITEMS);
+ }
+ firstVisibleIndex = begin;
+ }
+
+ public void scrollDown() {
+ if (firstVisibleIndex + 1 + MAX_SHOWN_NUMBER_OF_ITEMS <= dropdownLength()) {
+ firstVisibleIndex++;
+ }
+ if (selectedIndex < firstVisibleIndex) selectedIndex = firstVisibleIndex;
+ }
+ public void scrollUp() {
+ if (firstVisibleIndex > 0) {
+ firstVisibleIndex--;
+ }
+ if (selectedIndex > firstVisibleIndex + MAX_SHOWN_NUMBER_OF_ITEMS - 1) {
+ selectedIndex = firstVisibleIndex + MAX_SHOWN_NUMBER_OF_ITEMS - 1;
+ }
+ }
+
+
+ @Override
+ public void close() {
+ dropdownElement.removeDropdownWidget();
+ }
+
+ @Override
+ public Component popupTitle() {
+ return Component.translatable("yacl.control.dropdown.dropdown_widget_title");
+ }
+
+}
diff --git a/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/ItemControllerElement.java b/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/ItemControllerElement.java
index 1617c41..2c19c13 100644
--- a/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/ItemControllerElement.java
+++ b/src/main/java/dev/isxander/yacl3/gui/controllers/dropdown/ItemControllerElement.java
@@ -49,9 +49,13 @@ public class ItemControllerElement extends AbstractDropdownControllerElement<Ite
}
@Override
- protected void renderDropdownEntry(GuiGraphics graphics, ResourceLocation identifier, int n) {
- super.renderDropdownEntry(graphics, identifier, n);
- graphics.renderFakeItem(new ItemStack(matchingItems.get(identifier)), getDimension().xLimit() - getDecorationPadding() - 2, getDimension().y() + n * getDimension().height() + 4);
+ protected void renderDropdownEntry(GuiGraphics graphics, Dimension<Integer> entryDimension, ResourceLocation identifier) {
+ super.renderDropdownEntry(graphics, entryDimension, identifier);
+ graphics.renderFakeItem(
+ new ItemStack(matchingItems.get(identifier)),
+ entryDimension.xLimit() - 2,
+ entryDimension.y() + 1
+ );
}
@Override