diff --git a/src/main/java/ru/bclib/gui/gridlayout/GridLayout.java b/src/main/java/ru/bclib/gui/gridlayout/GridLayout.java index ca8cc4c1..9c4bce86 100644 --- a/src/main/java/ru/bclib/gui/gridlayout/GridLayout.java +++ b/src/main/java/ru/bclib/gui/gridlayout/GridLayout.java @@ -5,6 +5,7 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.gui.components.AbstractWidget; import ru.bclib.gui.gridlayout.GridLayout.GridValueType; +import ru.bclib.util.Pair; import ru.bclib.util.TriConsumer; import java.util.LinkedList; @@ -63,7 +64,7 @@ class GridElement extends GridTransform{ } GridTransform transformWithPadding(int leftPadding, int topPadding){ - return new GridTransform(left + leftPadding, top +topPadding, width, height); + return new GridTransform(left + leftPadding, top + topPadding, width, height); } } @@ -84,8 +85,10 @@ abstract class GridContainer extends GridCellDefinition{ @Environment(EnvType.CLIENT) public class GridLayout extends GridColumn { public static final int COLOR_WHITE = 0xFFFFFFFF; - public static final int COLOR_RED = 0xFFFF0000; + public static final int COLOR_RED = 0xFFDB1F48; + public static final int COLOR_CYAN = 0xFF01949A; public static final int COLOR_GREEN = 0xFF00FF00; + public static final int COLOR_YELLOW = 0xFFFAD02C; public static final int COLOR_BLUE = 0xFF0000FF; public static final int COLOR_GRAY = 0xFF7F7F7F; @@ -130,14 +133,15 @@ public class GridLayout extends GridColumn { elements = new LinkedList<>(); GridElement el = this.buildElement((int)this.width, 0, 1, 0,0, elements); this.height = el.height; - if (centerVertically) { + if (centerVertically && el.height + initialTopPadding < screenHeight) { topPadding = (screenHeight - el.height - initialTopPadding) >> 1; } else { topPadding = initialTopPadding; } } - + + public List> movableWidgets = new LinkedList<>(); public void finalizeLayout(){ buildLayout(); @@ -145,11 +149,14 @@ public class GridLayout extends GridColumn { .stream() .filter(element -> element.componentPlacer!=null) .forEach(element -> { - Object context = element.componentPlacer.apply(element.transformWithPadding(sidePadding, topPadding)); + final GridTransform transform = element.transformWithPadding(sidePadding, topPadding); + final Object context = element.componentPlacer.apply(transform); if (element.customRender != null){ element.renderContext = context; } else if (context instanceof AbstractWidget) { - screen.addRenderableWidget((AbstractWidget) context); + final AbstractWidget widget = (AbstractWidget)context; + movableWidgets.add(new Pair(widget, widget.y)); + screen.addRenderableWidget(widget); } }); } diff --git a/src/main/java/ru/bclib/gui/gridlayout/GridRow.java b/src/main/java/ru/bclib/gui/gridlayout/GridRow.java index e50c4bcd..650a49d2 100644 --- a/src/main/java/ru/bclib/gui/gridlayout/GridRow.java +++ b/src/main/java/ru/bclib/gui/gridlayout/GridRow.java @@ -199,7 +199,7 @@ public class GridRow extends GridContainer { public GridStringCell addString(Component text, int color, GridScreen parent) { final int width = parent.getWidth(text); - return this.addString(text, width, GridValueType.CONSTANT, GridLayout.COLOR_WHITE, Alignment.CENTER, parent); + return this.addString(text, width, GridValueType.CONSTANT, color, Alignment.CENTER, parent); } public GridStringCell addString(Component text, Alignment contentAlignment, GridScreen parent) { diff --git a/src/main/java/ru/bclib/gui/gridlayout/GridScreen.java b/src/main/java/ru/bclib/gui/gridlayout/GridScreen.java index 4653128e..b87a3486 100644 --- a/src/main/java/ru/bclib/gui/gridlayout/GridScreen.java +++ b/src/main/java/ru/bclib/gui/gridlayout/GridScreen.java @@ -1,14 +1,23 @@ package ru.bclib.gui.gridlayout; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.Tesselator; +import com.mojang.blaze3d.vertex.VertexFormat; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.AbstractSelectionList; +import net.minecraft.client.gui.components.OptionsList; import net.minecraft.client.gui.components.Widget; import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.narration.NarratableEntry; import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.renderer.GameRenderer; import net.minecraft.network.chat.Component; +import net.minecraft.util.Mth; import org.jetbrains.annotations.Nullable; import ru.bclib.gui.gridlayout.GridLayout.Alignment; @@ -21,6 +30,8 @@ public abstract class GridScreen extends Screen { public final boolean centerVertically; @Nullable public final Screen parent; + + protected int scrollPos = 0; public GridScreen(Component title) { this(null, title); @@ -65,8 +76,6 @@ public abstract class GridScreen extends Screen { return super.addRenderableWidget(guiEventListener); } - - protected void addTitle(){ grid.addRow().addString(this.title, Alignment.CENTER, this); grid.addSpacerRow(15); @@ -87,16 +96,32 @@ public abstract class GridScreen extends Screen { protected void renderScreen(PoseStack poseStack, int i, int j, float f) { super.render(poseStack, i, j, f); } - + public void render(PoseStack poseStack, int i, int j, float f) { - //this.renderBackground(poseStack); this.renderDirtBackground(i); - //drawCenteredString(poseStack, this.font, this.title, grid.width / 2, grid.getTopStart(), 16777215); - if (grid!=null) grid.render(poseStack); - + renderGrid(poseStack); super.render(poseStack, i, j, f); } - + + protected void renderGrid(PoseStack poseStack) { + if (grid!=null) { + if (isScrollable()) { + for (var item : grid.movableWidgets) { + item.first.y = item.second + scrollPos; + } + + renderScroll(poseStack); + + poseStack.pushPose(); + poseStack.translate(0, scrollPos, 0); + grid.render(poseStack); + poseStack.popPose(); + } else { + grid.render(poseStack); + } + } + } + public static int getWidth(Component text, Font font) { return font.width(text.getVisualOrderText()); } @@ -104,4 +129,127 @@ public abstract class GridScreen extends Screen { public int getWidth(Component text) { return getWidth(text, getFont()); } + + public void setScrollPos(int sp) { + scrollPos = Math.max(getMaxScrollPos(), Math.min(0, sp)); + } + + public int getScrollPos() { + return scrollPos; + } + + public int getScrollHeight() { + if (grid!=null) return grid.getHeight() + topPadding; + return height; + } + + public int getMaxScrollPos() { + return height-getScrollHeight(); + } + + public boolean isScrollable() { + if (grid==null) return false; + return height= 0 && y <= height && x >= width-SCROLLER_WIDTH && x <= width; + } + + private boolean scrolling = false; + protected void updateScrollingState(double x, double y, int i) { + this.scrolling = i == 0 && x >= width-SCROLLER_WIDTH && x < width; + } + + private static final int SCROLLER_WIDTH = 6; + private void renderScroll(PoseStack poseStack){ + final int y1 = height; + final int y0 = 0; + final int yd = y1 - y0; + final int maxPosition = getScrollHeight(); + + final int x0 = width-SCROLLER_WIDTH; + final int x1 = width; + + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder bufferBuilder = tesselator.getBuilder(); + RenderSystem.disableTexture(); + RenderSystem.setShader(GameRenderer::getPositionColorShader); + int widgetHeight = (int)((float)(yd*yd) / (float)maxPosition); + widgetHeight = Mth.clamp(widgetHeight, 32, yd - 8); + float relPos = (float)this.getScrollPos() / this.getMaxScrollPos(); + int top = (int)(relPos * (yd - widgetHeight)) + y0; + if (top < y0) { + top = y0; + } + + bufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); + + //scroller background + bufferBuilder.vertex((double)x0, (double)y1, 0.0D).color(0, 0, 0, 255).endVertex(); + bufferBuilder.vertex((double)x1, (double)y1, 0.0D).color(0, 0, 0, 255).endVertex(); + bufferBuilder.vertex((double)x1, (double)y0, 0.0D).color(0, 0, 0, 255).endVertex(); + bufferBuilder.vertex((double)x0, (double)y0, 0.0D).color(0, 0, 0, 255).endVertex(); + + //scroll widget shadow + bufferBuilder.vertex((double)x0, (double)(top + widgetHeight), 0.0D).color(128, 128, 128, 255).endVertex(); + bufferBuilder.vertex((double)x1, (double)(top + widgetHeight), 0.0D).color(128, 128, 128, 255).endVertex(); + bufferBuilder.vertex((double)x1, (double)top, 0.0D).color(128, 128, 128, 255).endVertex(); + bufferBuilder.vertex((double)x0, (double)top, 0.0D).color(128, 128, 128, 255).endVertex(); + + //scroll widget + bufferBuilder.vertex((double)x0, (double)(top + widgetHeight - 1), 0.0D).color(192, 192, 192, 255).endVertex(); + bufferBuilder.vertex((double)(x1 - 1), (double)(top + widgetHeight - 1), 0.0D).color(192, 192, 192, 255).endVertex(); + bufferBuilder.vertex((double)(x1 - 1), (double)top, 0.0D).color(192, 192, 192, 255).endVertex(); + bufferBuilder.vertex((double)x0, (double)top, 0.0D).color(192, 192, 192, 255).endVertex(); + tesselator.end(); + } + + public boolean mouseClicked(double x, double y, int i) { + this.updateScrollingState(x, y, i); + if (this.scrolling) { + return true; + } else { + return super.mouseClicked(x, y, i); + } + } + + public boolean mouseDragged(double xAbs, double yAbs, int i, double dX, double dY) { + if (super.mouseDragged(xAbs, yAbs, i, dX, dY)) { + return true; + } else if (i == 0 && this.scrolling) { + if (yAbs < 0) { + this.setScrollPos(0); + } else if (yAbs > height) { + this.setScrollPos(this.getMaxScrollPos()); + } else { + this.setScrollPos((int)(this.getScrollPos() - dY * 2)); + } + + return true; + } else { + return false; + } + } + + public boolean mouseScrolled(double d, double e, double f) { + if (isScrollable()) { + setScrollPos((int) (scrollPos + f * 10)); + } + return true; + } + + public boolean keyPressed(int keyCode, int j, int k) { + if (super.keyPressed(keyCode, j, k)) { + return true; + } else if (keyCode == 264) { + this.mouseScrolled(0, -1.0f, 0); + return true; + } else if (keyCode == 265) { + this.mouseScrolled(0, 1.0, 0); + return true; + } else { + return false; + } + } } diff --git a/src/main/java/ru/bclib/gui/screens/BCLibScreen.java b/src/main/java/ru/bclib/gui/screens/BCLibScreen.java index 89158355..b25c3bbe 100644 --- a/src/main/java/ru/bclib/gui/screens/BCLibScreen.java +++ b/src/main/java/ru/bclib/gui/screens/BCLibScreen.java @@ -47,23 +47,8 @@ abstract class BCLibScreen extends GridScreen { row.addFiller(); row.addImage(BCLIB_LOGO_LOCATION, 24, GridValueType.CONSTANT, 24, 512, 512); row.addSpacer(4); - row.addString(this.title, font.lineHeight,this); + row.addString(this.title, this); row.addFiller(); grid.addSpacerRow(15); } - - @Override - public void render(PoseStack poseStack, int i, int j, float f) { - this.renderDirtBackground(i); -// -// RenderSystem.setShader(GameRenderer::getPositionTexShader); -// RenderSystem.setShaderTexture(0, BCLIB_LOGO_LOCATION); -// RenderSystem.enableBlend(); -// RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); -// RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0f); -// blit(poseStack, 0, 0, 32, 32, 0, 0, 512, 512, 512, 512); - - if (grid!=null) grid.render(poseStack); - super.renderScreen(poseStack, i, j, f); - } } diff --git a/src/main/java/ru/bclib/gui/screens/ModListScreen.java b/src/main/java/ru/bclib/gui/screens/ModListScreen.java new file mode 100644 index 00000000..9c05ffc5 --- /dev/null +++ b/src/main/java/ru/bclib/gui/screens/ModListScreen.java @@ -0,0 +1,182 @@ +package ru.bclib.gui.screens; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.loader.api.metadata.ModEnvironment; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TextComponent; +import ru.bclib.api.dataexchange.handler.autosync.HelloClient; +import ru.bclib.gui.gridlayout.GridColumn; +import ru.bclib.gui.gridlayout.GridLayout; +import ru.bclib.gui.gridlayout.GridRow; +import ru.bclib.gui.gridlayout.GridScreen; +import ru.bclib.util.ModUtil; +import ru.bclib.util.Pair; +import ru.bclib.util.PathUtil; +import ru.bclib.util.Triple; + +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +@Environment(EnvType.CLIENT) +public class ModListScreen extends BCLibScreen { + + private final List mods; + private final HelloClient.IServerModMap serverInfo; + private final Component description; + + private static List extractModList(Map mods){ + List list = new LinkedList(); + ModUtil.getMods().forEach((id, info) -> list.add(info)); + return list; + } + + public ModListScreen(Screen parent, Component title, Component description, Map mods, HelloClient.IServerModMap serverInfo) { + this(parent, title, description, extractModList(mods), serverInfo); + } + + public ModListScreen(Screen parent, Component title, Component description, List mods, HelloClient.IServerModMap serverInfo) { + super(parent, title, 10, true); + this.mods = mods; + this.serverInfo = serverInfo; + this.description = description; + } + + public static void addModDesc(GridColumn grid, java.util.List mods, HelloClient.IServerModMap serverInfo, GridScreen parent) { + final int STATE_OK = 0; + final int STATE_MISSING = 1; + final int STATE_SERVER_MISSING = 2; + final int STATE_VERSION = 3; + final int STATE_SERVER_MISSING_CLIENT_MOD = 4; + + List> items = new LinkedList<>(); + if (serverInfo!=null) { + serverInfo.keySet() + .stream() + .filter(modid -> !mods.stream().filter(mod -> mod.metadata.getId().equals(modid)).findFirst().isPresent()) + .forEach(modid -> { + int size = serverInfo.get(modid).second; + String stateString = serverInfo.get(modid).first; + if (size>0) { + stateString = "Version: " + stateString + ", Size: " + PathUtil.humanReadableFileSize(size); + } + + items.add(new Triple<>(modid, STATE_MISSING, stateString)); + }); + } + + mods.forEach(mod -> { + String serverVersion = null; + int serverSize = 0; + int state = STATE_OK; + if (serverInfo != null) { + final String modID = mod.metadata.getId(); + + + Pair data = serverInfo.get(modID); + if (data!=null) { + final String modVer = data.first; + final int size = data.second; + if (!modVer.equals(mod.getVersion())) { + state = STATE_VERSION; + serverVersion = modVer; + serverSize = size; + } + } else if (mod.metadata.getEnvironment() == ModEnvironment.CLIENT){ + state = STATE_SERVER_MISSING_CLIENT_MOD; + } else { + state = STATE_SERVER_MISSING; + } + } + + String stateString = mod.metadata.getVersion().toString(); + if (serverVersion!=null) { + stateString = "Client: " + stateString; + stateString += ", Server: " + serverVersion; + if (serverSize>0) { + stateString += ", Size: " + PathUtil.humanReadableFileSize(serverSize); + } + } + if (mod.metadata.getEnvironment() == ModEnvironment.CLIENT) { + stateString+= ", client-only"; + } else if (mod.metadata.getEnvironment() == ModEnvironment.SERVER) { + stateString+= ", server-only"; + } + items.add(new Triple<>(mod.metadata.getName(), state, stateString)); + }); + + items.stream() + .sorted(Comparator.comparing(a -> a.first.toLowerCase(Locale.ROOT))) + .forEach(t -> { + final String name = t.first; + final int state = t.second; + final String stateString = t.third; + + int color = GridLayout.COLOR_RED; + final String typeText; + if (state==STATE_VERSION) { + typeText = "[VERSION]"; + } else if (state==STATE_MISSING) { + typeText = "[MISSING]"; + } else if (state==STATE_SERVER_MISSING || state == STATE_SERVER_MISSING_CLIENT_MOD) { + typeText = "[NOT ON SERVER]"; + if (state == STATE_SERVER_MISSING_CLIENT_MOD) { + color = GridLayout.COLOR_YELLOW; + } + } else { + color = GridLayout.COLOR_CYAN; + typeText = "[OK]"; + } + TextComponent dash = new TextComponent("-"); + TextComponent typeTextComponent = new TextComponent(typeText); + GridRow row = grid.addRow(); + + row.addString(dash, parent); + + row.addSpacer(4); + row.addString(new TextComponent(name), parent); + + row.addSpacer(4); + row.addString(typeTextComponent, color, parent); + + if (!stateString.isEmpty()) { + row = grid.addRow(); + row.addSpacer(4 + parent.getWidth(dash)); + row.addString(new TextComponent(stateString), GridLayout.COLOR_GRAY, parent); + } + + grid.addSpacerRow(); + }); + } + + @Override + protected void initLayout() { + if (description != null) { + grid.addSpacerRow(); + grid.addRow().addMessage(description, font, GridLayout.Alignment.CENTER); + grid.addSpacerRow(20); + } + + GridRow row = grid.addRow(); + row.addSpacer(10); + GridColumn col = row.addColumn(200, GridLayout.GridValueType.CONSTANT); + addModDesc(col, mods, serverInfo, this); + + grid.addSpacerRow(10); + row = grid.addRow(); + row.addFiller(); + row.addButton(CommonComponents.GUI_BACK, 20, font, (n)-> { + onClose(); + System.out.println("Closing"); + }); + row.addFiller(); + + grid.addSpacerRow(topPadding); + } + +}