diff --git a/src/main/java/net/vulkanmod/config/gui/AbstractScrollList.java b/src/main/java/net/vulkanmod/config/gui/AbstractScrollList.java new file mode 100644 index 000000000..67ff0973c --- /dev/null +++ b/src/main/java/net/vulkanmod/config/gui/AbstractScrollList.java @@ -0,0 +1,297 @@ +package net.vulkanmod.config.gui; + +import com.mojang.blaze3d.opengl.GlStateManager; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.input.MouseButtonEvent; +import net.minecraft.util.Mth; +import net.vulkanmod.config.gui.render.GuiRenderer; +import net.vulkanmod.config.gui.widget.VAbstractWidget; +import net.vulkanmod.vulkan.util.ColorUtil; +import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NonNull; + +import java.util.List; + +public abstract class AbstractScrollList extends GuiElement { + protected final List children = new ObjectArrayList<>(); + protected boolean scrolling = false; + protected float scrollAmount = 0.0f; + protected int itemWidth; + protected int totalItemHeight; + protected int itemMargin; + protected int listLength = 0; + protected Entry focused; + + protected AbstractScrollList() { + } + + protected void addEntry(Entry entry) { + this.children.add(entry); + this.listLength += entry.getTotalHeight(); + } + + public void clearEntries() { + this.listLength = 0; + this.children.clear(); + } + + protected void updateScrollingState(double mouseX, int button) { + this.scrolling = button == 0 && mouseX >= (double) this.getScrollbarPosition() && mouseX < (double) (this.getScrollbarPosition() + 6); + } + + protected float getScrollAmount() { + return scrollAmount; + } + + public void setScrollAmount(double d) { + this.scrollAmount = (float) Mth.clamp(d, 0.0, this.getMaxScroll()); + } + + protected int getItemCount() { + return this.children.size(); + } + + protected GuiEventListener getFocused() { + return focused; + } + + protected void setFocused(Entry focussed) { + this.focused = focussed; + } + + @Override + public boolean mouseClicked(MouseButtonEvent event, boolean bl) { + this.updateScrollingState(event.x(), event.button()); + if (this.isMouseOver(event.x(), event.y())) { + Entry entry = this.getEntryAtPos(event.x(), event.y()); + if (entry != null && entry.mouseClicked(event, bl)) { + setFocused(entry); + entry.setFocused(true); + return true; + } + + return event.button() == 0; + } + + return false; + } + + @Override + public boolean mouseReleased(MouseButtonEvent event) { + if (this.isValidClickButton(event.button())) { + Entry entry = this.getEntryAtPos(event.x(), event.y()); + if (entry != null) { + if (entry.mouseReleased(event)) { + entry.setFocused(false); + setFocused(null); + return true; + } + } + } + return false; + } + + @Override + public boolean mouseDragged(MouseButtonEvent event, double deltaX, double deltaY) { + if (event.button() != 0) { + return false; + } + + if (this.getFocused() != null) { + return this.getFocused().mouseDragged(event, deltaX, deltaY); + } + + if (!this.scrolling) { + return false; + } + + double maxScroll = this.getMaxScroll(); + if (event.y() < this.y) { + this.setScrollAmount(0.0); + } else if (event.y() > this.getBottom()) { + this.setScrollAmount(maxScroll); + } else if (maxScroll > 0.0) { + double barHeight = (double) this.height * this.height / this.getTotalLength(); + double scrollFactor = Math.max(1.0, maxScroll / (this.height - barHeight)); + this.setScrollAmount(this.getScrollAmount() + deltaY * scrollFactor); + } + + return true; + } + + public boolean mouseScrolled(double mouseX, double mouseY, double xScroll, double yScroll) { + this.setScrollAmount(this.getScrollAmount() - yScroll * (double) this.totalItemHeight / 2.0); + return true; + } + + public int getMaxScroll() { + return Math.max(0, this.getTotalLength() - (this.height)); + } + + protected int getTotalLength() { + return this.listLength; + } + + public int getBottom() { + return this.y + this.height; + } + + @Nullable + protected Entry getEntryAtPos(double x, double y) { + int x0 = this.x; + + if (x > this.getScrollbarPosition() || x < (double) x0) + return null; + + for (var entry : this.children) { + VAbstractWidget widget = entry.widget; + if (widget != null && y >= widget.y && y <= widget.y + widget.height) { + return entry; + } + } + return null; + } + + @Override + public void updateState(double mX, double mY) { + if (this.focused != null) + return; + + super.updateState(mX, mY); + } + + public void renderWidget(int mouseX, int mouseY) { + GuiRenderer.enableScissor(x, y, x + width, y + height); + + this.renderList(mouseX, mouseY); + GuiRenderer.disableScissor(); + + // Scroll bar + int maxScroll = this.getMaxScroll(); + if (maxScroll > 0) { + GlStateManager._enableBlend(); + + int height = this.getHeight(); + int totalLength = this.getTotalLength(); + int barHeight = (int) ((float) (height * height) / totalLength); + barHeight = Mth.clamp(barHeight, 32, height - 8); + + int scrollAmount = (int) this.getScrollAmount(); + int barY = scrollAmount * (height - barHeight) / maxScroll + this.getY(); + barY = Math.max(barY, this.getY()); + + int scrollbarPosition = this.getScrollbarPosition(); + int thickness = 3; + + int backgroundColor = ColorUtil.ARGB.pack(0.8f, 0.8f, 0.8f, 0.2f); + GuiRenderer.fill(scrollbarPosition, this.getY(), scrollbarPosition + thickness, this.getY() + height, backgroundColor); + + int barColor = ColorUtil.ARGB.pack(0.3f, 0.0f, 0.0f, 0.6f); + GuiRenderer.fill(scrollbarPosition, barY, scrollbarPosition + thickness, barY + barHeight, barColor); + } + } + + protected int getScrollbarPosition() { + return this.x + this.width; + } + + public VAbstractWidget getHoveredWidget(double mouseX, double mouseY) { + if (this.focused != null) + return focused.widget; + + if (!this.isMouseOver(mouseX, mouseY)) + return null; + + for (Entry entry : this.children) { + var widget = entry.widget; + + if (widget == null || !widget.isMouseOver(mouseX, mouseY)) + continue; + return widget; + } + return null; + } + + protected void renderList(int mouseX, int mouseY) { + int itemCount = this.getItemCount(); + + int rowTop = this.y - (int) this.getScrollAmount(); + for (int j = 0; j < itemCount; ++j) { + Entry entry = this.getEntry(j); + + if (rowTop + entry.getTotalHeight() >= this.y && rowTop <= (this.y + this.height)) { + boolean updateState = this.focused == null; + entry.render(rowTop, mouseX, mouseY, updateState, this.x); + } + + rowTop += entry.getTotalHeight(); + } + } + + protected Entry getEntry(int j) { + return this.children.get(j); + } + + protected boolean isValidClickButton(int i) { + return i == 0; + } + + protected static class Entry implements GuiEventListener { + final VAbstractWidget widget; + final int margin; + + protected Entry(VAbstractWidget widget, int margin) { + this.widget = widget; + this.margin = margin; + } + + public void render(int y, int mouseX, int mouseY, boolean updateState, int listX) { + if (widget == null) + return; + + widget.y = y; + + if (updateState) + widget.updateState(mouseX, mouseY); + + widget.render(mouseX, mouseY); + } + + public int getTotalHeight() { + if (widget != null) + return widget.height + margin; + else + return margin; + } + + @Override + public boolean mouseClicked(@NonNull MouseButtonEvent event, boolean bl) { + if (widget == null) return false; + return widget.mouseClicked(event, bl); + } + + @Override + public boolean mouseReleased(@NonNull MouseButtonEvent event) { + if (widget == null) return false; + return widget.mouseReleased(event); + } + + @Override + public boolean mouseDragged(@NonNull MouseButtonEvent event, double deltaX, double deltaY) { + if (widget == null) return false; + return widget.mouseDragged(event, deltaX, deltaY); + } + + @Override + public boolean isFocused() { + return false; + } + + @Override + public void setFocused(boolean bl) { + if (widget != null) + widget.setFocused(bl); + } + } +} \ No newline at end of file diff --git a/src/main/java/net/vulkanmod/config/gui/VOptionList.java b/src/main/java/net/vulkanmod/config/gui/VOptionList.java index 9b441c9e7..e4b304e16 100644 --- a/src/main/java/net/vulkanmod/config/gui/VOptionList.java +++ b/src/main/java/net/vulkanmod/config/gui/VOptionList.java @@ -1,31 +1,13 @@ package net.vulkanmod.config.gui; -import com.mojang.blaze3d.opengl.GlStateManager; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.components.events.GuiEventListener; -import net.minecraft.client.input.MouseButtonEvent; import net.minecraft.network.chat.Component; -import net.minecraft.util.Mth; import net.vulkanmod.config.gui.render.GuiRenderer; import net.vulkanmod.config.gui.widget.OptionWidget; -import net.vulkanmod.config.gui.widget.VAbstractWidget; import net.vulkanmod.config.option.Option; -import net.vulkanmod.vulkan.util.ColorUtil; -import org.jetbrains.annotations.Nullable; -import java.util.List; - -public class VOptionList extends GuiElement { - private final List children = new ObjectArrayList<>(); - boolean scrolling = false; - float scrollAmount = 0.0f; - int itemWidth; - int totalItemHeight; +public class VOptionList extends AbstractScrollList { int itemHeight; - int itemMargin; - int listLength = 0; - Entry focused; public VOptionList(int x, int y, int width, int height, int itemHeight) { this.setPosition(x, y, width, height); @@ -68,229 +50,15 @@ public void addAll(OptionBlock[] blocks) { } } - private void addEntry(Entry entry) { - this.children.add(entry); - this.listLength += entry.getTotalHeight(); - } - - @SuppressWarnings("unused") - public void clearEntries() { - this.listLength = 0; - this.children.clear(); - } - - protected void updateScrollingState(double mouseX, int button) { - this.scrolling = button == 0 && mouseX >= (double) this.getScrollbarPosition() && mouseX < (double) (this.getScrollbarPosition() + 6); - } - - protected float getScrollAmount() { - return scrollAmount; - } - - public void setScrollAmount(double d) { - this.scrollAmount = (float) Mth.clamp(d, 0.0, this.getMaxScroll()); - } - - private int getItemCount() { - return this.children.size(); - } - - GuiEventListener getFocused() { - return focused; - } - - void setFocused(Entry focussed) { - this.focused = focussed; - } - - @Override - public boolean mouseClicked(MouseButtonEvent event, boolean bl) { - this.updateScrollingState(event.x(), event.button()); - if (this.isMouseOver(event.x(), event.y())) { - Entry entry = this.getEntryAtPos(event.x(), event.y()); - if (entry != null && entry.mouseClicked(event, bl)) { - setFocused(entry); - entry.setFocused(true); - return true; - } - - return event.button() == 0; - } - - return false; - } - - @Override - public boolean mouseReleased(MouseButtonEvent event) { - if (this.isValidClickButton(event.button())) { - Entry entry = this.getEntryAtPos(event.x(), event.y()); - if (entry != null) { - if (entry.mouseReleased(event)) { - entry.setFocused(false); - setFocused(null); - return true; - } - } - } - return false; - } - - @Override - public boolean mouseDragged(MouseButtonEvent event, double deltaX, double deltaY) { - if (event.button() != 0) { - return false; - } - - if (this.getFocused() != null) { - return this.getFocused().mouseDragged(event, deltaX, deltaY); - } - - if (!this.scrolling) { - return false; - } - - double maxScroll = this.getMaxScroll(); - if (event.y() < this.y) { - this.setScrollAmount(0.0); - } else if (event.y() > this.getBottom()) { - this.setScrollAmount(maxScroll); - } else if (maxScroll > 0.0) { - double barHeight = (double) this.height * this.height / this.getTotalLength(); - double scrollFactor = Math.max(1.0, maxScroll / (this.height - barHeight)); - this.setScrollAmount(this.getScrollAmount() + deltaY * scrollFactor); - } - - return true; - } - - public boolean mouseScrolled(double mouseX, double mouseY, double xScroll, double yScroll) { - this.setScrollAmount(this.getScrollAmount() - yScroll * (double) this.totalItemHeight / 2.0); - return true; - } - - public int getMaxScroll() { - return Math.max(0, this.getTotalLength() - (this.height)); - } - - protected int getTotalLength() { - return this.listLength; - } - - public int getBottom() { - return this.y + this.height; - } - - @Nullable - protected VOptionList.Entry getEntryAtPos(double x, double y) { - int x0 = this.x; - - if (x > this.getScrollbarPosition() || x < (double) x0) - return null; - - for (var entry : this.children) { - VAbstractWidget widget = entry.widget; - if (widget != null && y >= widget.y && y <= widget.y + widget.height) { - return entry; - } - } - return null; - } - - @Override - public void updateState(double mX, double mY) { - if (this.focused != null) - return; - - super.updateState(mX, mY); - } - - public void renderWidget(int mouseX, int mouseY) { - GuiRenderer.enableScissor(x, y, x + width, y + height); - - this.renderList(mouseX, mouseY); - GuiRenderer.disableScissor(); - - // Scroll bar - int maxScroll = this.getMaxScroll(); - if (maxScroll > 0) { - GlStateManager._enableBlend(); - - int height = this.getHeight(); - int totalLength = this.getTotalLength(); - int barHeight = (int) ((float) (height * height) / totalLength); - barHeight = Mth.clamp(barHeight, 32, height - 8); - - int scrollAmount = (int) this.getScrollAmount(); - int barY = scrollAmount * (height - barHeight) / maxScroll + this.getY(); - barY = Math.max(barY, this.getY()); - - int scrollbarPosition = this.getScrollbarPosition(); - int thickness = 3; - - int backgroundColor = ColorUtil.ARGB.pack(0.8f, 0.8f, 0.8f, 0.2f); - GuiRenderer.fill(scrollbarPosition, this.getY(), scrollbarPosition + thickness, this.getY() + height, backgroundColor); - - int barColor = ColorUtil.ARGB.pack(0.3f, 0.0f, 0.0f, 0.6f); - GuiRenderer.fill(scrollbarPosition, barY, scrollbarPosition + thickness, barY + barHeight, barColor); - } - } - - protected int getScrollbarPosition() { - return this.x + this.width; - } - - public VAbstractWidget getHoveredWidget(double mouseX, double mouseY) { - if (this.focused != null) - return focused.widget; - - if (!this.isMouseOver(mouseX, mouseY)) - return null; - - for (VOptionList.Entry entry : this.children) { - var widget = entry.widget; - - if (widget == null || !widget.isMouseOver(mouseX, mouseY)) - continue; - return widget; - } - return null; - } - - protected void renderList(int mouseX, int mouseY) { - int itemCount = this.getItemCount(); - - int rowTop = this.y - (int) this.getScrollAmount(); - for (int j = 0; j < itemCount; ++j) { - VOptionList.Entry entry = this.getEntry(j); - - if (rowTop + entry.getTotalHeight() >= this.y && rowTop <= (this.y + this.height)) { - boolean updateState = this.focused == null; - entry.render(rowTop, mouseX, mouseY, updateState, this.x); - } - - rowTop += entry.getTotalHeight(); - } - } - - private Entry getEntry(int j) { - return this.children.get(j); - } - - protected boolean isValidClickButton(int i) { - return i == 0; - } - - protected static class Entry implements GuiEventListener { - final VAbstractWidget widget; - final int margin; + protected static class Entry extends AbstractScrollList.Entry { final String headerTitle; private Entry(OptionWidget widget, int margin, String headerTitle) { - this.widget = widget; - this.margin = margin; + super(widget, margin); this.headerTitle = headerTitle; } + @Override public void render(int y, int mouseX, int mouseY, boolean updateState, int listX) { // if there is a title, RENDER IT!!! if (headerTitle != null && !headerTitle.isEmpty()) { @@ -305,54 +73,15 @@ public void render(int y, int mouseX, int mouseY, boolean updateState, int listX return; } - if (widget == null) - return; - - widget.y = y; - - if (updateState) - widget.updateState(mouseX, mouseY); - - widget.render(mouseX, mouseY); + super.render(y, mouseX, mouseY, updateState, listX); } + @Override public int getTotalHeight() { if (headerTitle != null && !headerTitle.isEmpty()) { return Minecraft.getInstance().font.lineHeight + margin; } - if (widget != null) - return widget.height + margin; - else - return margin; - } - - @Override - public boolean mouseClicked(MouseButtonEvent event, boolean bl) { - if (widget == null) return false; - return widget.mouseClicked(event, bl); - } - - @Override - public boolean mouseReleased(MouseButtonEvent event) { - if (widget == null) return false; - return widget.mouseReleased(event); - } - - @Override - public boolean mouseDragged(MouseButtonEvent event, double deltaX, double deltaY) { - if (widget == null) return false; - return widget.mouseDragged(event, deltaX, deltaY); - } - - @Override - public boolean isFocused() { - return false; - } - - @Override - public void setFocused(boolean bl) { - if (widget != null) - widget.setFocused(bl); + return super.getTotalHeight(); } } } \ No newline at end of file diff --git a/src/main/java/net/vulkanmod/config/gui/VOptionScreen.java b/src/main/java/net/vulkanmod/config/gui/VOptionScreen.java index 3e81e17ba..4e6d78d46 100644 --- a/src/main/java/net/vulkanmod/config/gui/VOptionScreen.java +++ b/src/main/java/net/vulkanmod/config/gui/VOptionScreen.java @@ -50,9 +50,9 @@ public class VOptionScreen extends Screen { private VTextInputWidget searchField; - private final List iconWidgets = Lists.newArrayList(); + private VPageList pageList; + private final List pageButtons = Lists.newArrayList(); - private final List buttons = Lists.newArrayList(); public VOptionScreen(Component title, Screen parent) { @@ -210,9 +210,7 @@ else if (option instanceof CyclingOption cycling) { } private void buildPage() { - this.buttons.clear(); this.pageButtons.clear(); - this.iconWidgets.clear(); String savedInput = this.searchField != null ? this.searchField.getInput() : ""; boolean savedFocused = this.searchField != null && this.searchField.focused; @@ -220,33 +218,12 @@ private void buildPage() { this.clearWidgets(); - int x = MARGIN; - int y = 4; - - int width = VGuiConstants.PAGE_BUTTON_WIDTH; - int j = 0; - for (var modEntry : this.modSettingsEntries) { - ModIconWidget iconWidget = new ModIconWidget(modEntry.modName, modEntry.getIcon(), x, y, width, 28); - this.iconWidgets.add(iconWidget); - this.addWidget(iconWidget); - y += 28; - - var pages = modEntry.getPages(); - for (OptionPage page : pages) { - final int finalIdx = j; - VButtonWidget widget = new VButtonWidget(x, y, width, VGuiConstants.WIDGET_HEIGHT, Component.nullToEmpty(page.name), button -> this.setOptionList(finalIdx)); - widget.setTextLayout(false, 12); - this.buttons.add(widget); - this.pageButtons.add(widget); - this.addWidget(widget); - - y += VGuiConstants.WIDGET_HEIGHT; - j++; - } - } + this.pageList = new VPageList(MARGIN, 4, VGuiConstants.PAGE_BUTTON_WIDTH, this.height - MARGIN, this::setOptionList); + this.pageList.addAll(this.modSettingsEntries); + this.addWidget(this.pageList); if (!isSearchActive) { - this.pageButtons.get(this.currentListIdx).setSelected(true); + this.pageList.buttons.get(this.currentListIdx).setSelected(true); VOptionList currentList = this.optionPages.get(this.currentListIdx).getOptionList(); this.addWidget(currentList); } else { @@ -294,10 +271,10 @@ private void addButtonsWithSearchBar() { Component.translatable("vulkanmod.options.buttons.kofi"), button -> Util.getPlatform().openUri("https://ko-fi.com/xcollateral")); - this.buttons.add(this.applyButton); - this.buttons.add(doneButton); - this.buttons.add(supportButton); - this.buttons.add(this.undoButton); + this.pageButtons.add(this.applyButton); + this.pageButtons.add(doneButton); + this.pageButtons.add(supportButton); + this.pageButtons.add(this.undoButton); this.addWidget(this.applyButton); this.addWidget(doneButton); @@ -314,7 +291,7 @@ private void addButtonsWithSearchBar() { Component.translatable("vulkanmod.options.buttons.update_available").withStyle(ChatFormatting.UNDERLINE), button -> Util.getPlatform().openUri("https://modrinth.com/mod/vulkanmod") ); - this.buttons.add(updateButton); + this.pageButtons.add(updateButton); this.addWidget(updateButton); } } @@ -365,22 +342,22 @@ public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float delta) currentList.updateState(mouseX, mouseY); currentList.renderWidget(mouseX, mouseY); - for (var widget : iconWidgets) { - widget.render(mouseX, mouseY); - } + pageList.updateState(mouseX, mouseY); + pageList.renderWidget(mouseX, mouseY); - for (VButtonWidget button : buttons) { + for (VButtonWidget button : pageButtons) { button.updateState(mouseX, mouseY); button.render(mouseX, mouseY); } + searchField.updateState(mouseX, mouseY); searchField.render(mouseX, mouseY); VAbstractWidget hoveredWidget = null; - for (var b : buttons) { - if (b.isMouseOver(mouseX, mouseY)) { - hoveredWidget = b; + for (VButtonWidget button : pageButtons) { + if (button.isMouseOver(mouseX, mouseY)) { + hoveredWidget = button; break; } } @@ -462,7 +439,7 @@ private void setOptionList(int i) { this.buildPage(); - this.pageButtons.get(i).setSelected(true); + this.pageList.buttons.get(i).setSelected(true); } private void undo() { diff --git a/src/main/java/net/vulkanmod/config/gui/VPageList.java b/src/main/java/net/vulkanmod/config/gui/VPageList.java new file mode 100644 index 000000000..d46c40edc --- /dev/null +++ b/src/main/java/net/vulkanmod/config/gui/VPageList.java @@ -0,0 +1,59 @@ +package net.vulkanmod.config.gui; + +import net.minecraft.network.chat.Component; +import net.vulkanmod.config.gui.util.VGuiConstants; +import net.vulkanmod.config.gui.widget.ModIconWidget; +import net.vulkanmod.config.gui.widget.VAbstractWidget; +import net.vulkanmod.config.gui.widget.VButtonWidget; +import net.vulkanmod.config.option.OptionPage; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public class VPageList extends AbstractScrollList { + List buttons = new ArrayList<>(); + + final Consumer onSetOptionList; + + public VPageList(int x, int y, int width, int height, Consumer onSetOptionList) { + this.setPosition(x, y, width, height); + + this.width = width; + this.height = height; + + this.itemWidth = this.width - 7; + this.itemMargin = 0; + this.totalItemHeight = VGuiConstants.WIDGET_HEIGHT + this.itemMargin; + + this.onSetOptionList = onSetOptionList; + } + + @SuppressWarnings("unused") + public void addButton(VAbstractWidget widget) { + this.addEntry(new Entry(widget, this.itemMargin)); + } + + private void setOptionList(int finalIdx) { + this.onSetOptionList.accept(finalIdx); + } + + public void addAll(List modSettingsEntries) { + int width = VGuiConstants.PAGE_BUTTON_WIDTH; + int j = 0; + for (var modEntry : modSettingsEntries) { + ModIconWidget iconWidget = new ModIconWidget(modEntry.modName, modEntry.getIcon(), x, y, width, 28); + this.addButton(iconWidget); + + var pages = modEntry.getPages(); + for (OptionPage page : pages) { + final int finalIdx = j; + VButtonWidget widget = new VButtonWidget(x, y, width, VGuiConstants.WIDGET_HEIGHT, Component.nullToEmpty(page.name), button -> this.setOptionList(finalIdx)); + widget.setTextLayout(false, 12); + this.buttons.add(widget); + this.addButton(widget); + j++; + } + } + } +} \ No newline at end of file