Scrollable GridScreen

This commit is contained in:
Frank 2021-08-25 13:47:08 +02:00
parent f3bdaaac8e
commit 9d7fa9f925
5 changed files with 353 additions and 31 deletions

View file

@ -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,7 +133,7 @@ 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;
@ -138,6 +141,7 @@ public class GridLayout extends GridColumn {
}
public List<Pair<AbstractWidget, Integer>> 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);
}
});
}

View file

@ -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) {

View file

@ -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;
@ -22,6 +31,8 @@ public abstract class GridScreen extends Screen {
@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);
@ -89,14 +98,30 @@ public abstract class GridScreen extends Screen {
}
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<grid.getHeight();
}
public boolean isMouseOverScroller(double x, double y) {
return y >= 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;
}
}
}

View file

@ -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);
}
}

View file

@ -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<ModUtil.ModInfo> mods;
private final HelloClient.IServerModMap serverInfo;
private final Component description;
private static List<ModUtil.ModInfo> extractModList(Map<String, ModUtil.ModInfo> mods){
List<ModUtil.ModInfo> list = new LinkedList<ModUtil.ModInfo>();
ModUtil.getMods().forEach((id, info) -> list.add(info));
return list;
}
public ModListScreen(Screen parent, Component title, Component description, Map<String, ModUtil.ModInfo> mods, HelloClient.IServerModMap serverInfo) {
this(parent, title, description, extractModList(mods), serverInfo);
}
public ModListScreen(Screen parent, Component title, Component description, List<ModUtil.ModInfo> 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<ModUtil.ModInfo> 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<Triple<String, Integer, String>> 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<String, Integer> 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);
}
}