diff --git a/common/src/main/java/com/github/epsilon/graphics/LuminRenderPipelines.java b/common/src/main/java/com/github/epsilon/graphics/LuminRenderPipelines.java index 569b5caa..2de56869 100644 --- a/common/src/main/java/com/github/epsilon/graphics/LuminRenderPipelines.java +++ b/common/src/main/java/com/github/epsilon/graphics/LuminRenderPipelines.java @@ -45,6 +45,14 @@ public class LuminRenderPipelines { .withCull(false) .build(); + public final static RenderPipeline ROUND_RECT_OUTLINE = RenderPipeline.builder(NO_BLEND_DEPTH_SNIPPET) + .withLocation(ResourceLocationUtils.getIdentifier("pipelines/round_rectangle_outline")) + .withVertexFormat(LuminVertexFormats.ROUND_RECT_OUTLINE, VertexFormat.Mode.QUADS) + .withVertexShader(ResourceLocationUtils.getIdentifier("round_rectangle_outline")) + .withFragmentShader(ResourceLocationUtils.getIdentifier("round_rectangle_outline")) + .withCull(false) + .build(); + public final static RenderPipeline SHADOW = RenderPipeline.builder(NO_BLEND_DEPTH_SNIPPET) .withLocation(ResourceLocationUtils.getIdentifier("pipelines/shadow")) .withVertexFormat(LuminVertexFormats.ROUND_RECT, VertexFormat.Mode.QUADS) diff --git a/common/src/main/java/com/github/epsilon/graphics/LuminVertexFormats.java b/common/src/main/java/com/github/epsilon/graphics/LuminVertexFormats.java index 81a57517..40b80b6d 100644 --- a/common/src/main/java/com/github/epsilon/graphics/LuminVertexFormats.java +++ b/common/src/main/java/com/github/epsilon/graphics/LuminVertexFormats.java @@ -7,9 +7,11 @@ public class LuminVertexFormats { private static final int ROUND_INNER_RECT_ID = findNextId(); private static final int ROUND_RADIUS_ID = findNextId(ROUND_INNER_RECT_ID + 1); + private static final int ROUND_OUTLINE_WIDTH_ID = findNextId(ROUND_RADIUS_ID + 1); public static final VertexFormatElement ROUND_INNER_RECT = VertexFormatElement.register(ROUND_INNER_RECT_ID, 2, VertexFormatElement.Type.FLOAT, false, 4); public static final VertexFormatElement ROUND_RADIUS = VertexFormatElement.register(ROUND_RADIUS_ID, 4, VertexFormatElement.Type.FLOAT, false, 4); + public static final VertexFormatElement ROUND_OUTLINE_WIDTH = VertexFormatElement.register(ROUND_OUTLINE_WIDTH_ID, 1, VertexFormatElement.Type.FLOAT, false, 1); public static final VertexFormat ROUND_RECT = VertexFormat.builder() .add("Position", VertexFormatElement.POSITION) @@ -18,6 +20,14 @@ public class LuminVertexFormats { .add("Radius", ROUND_RADIUS) .build(); + public static final VertexFormat ROUND_RECT_OUTLINE = VertexFormat.builder() + .add("Position", VertexFormatElement.POSITION) + .add("Color", VertexFormatElement.COLOR) + .add("InnerRect", ROUND_INNER_RECT) + .add("Radius", ROUND_RADIUS) + .add("OutlineWidth", ROUND_OUTLINE_WIDTH) + .build(); + public static final VertexFormat TEXTURE = VertexFormat.builder() .add("Position", VertexFormatElement.POSITION) .add("Color", VertexFormatElement.COLOR) diff --git a/common/src/main/java/com/github/epsilon/graphics/renderers/RoundRectOutlineRenderer.java b/common/src/main/java/com/github/epsilon/graphics/renderers/RoundRectOutlineRenderer.java new file mode 100644 index 00000000..831cce41 --- /dev/null +++ b/common/src/main/java/com/github/epsilon/graphics/renderers/RoundRectOutlineRenderer.java @@ -0,0 +1,149 @@ +package com.github.epsilon.graphics.renderers; + +import com.github.epsilon.graphics.LuminRenderPipelines; +import com.github.epsilon.graphics.LuminRenderSystem; +import com.github.epsilon.graphics.buffer.LuminRingBuffer; +import com.mojang.blaze3d.buffers.GpuBuffer; +import com.mojang.blaze3d.systems.RenderPass; +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.util.ARGB; +import org.lwjgl.system.MemoryUtil; + +import java.awt.*; +import java.util.OptionalDouble; +import java.util.OptionalInt; + +public class RoundRectOutlineRenderer implements IRenderer { + + private static final long BUFFER_SIZE = 2 * 1024 * 1024; + private static final int STRIDE = 52; + + private final LuminRingBuffer buffer = new LuminRingBuffer(BUFFER_SIZE, GpuBuffer.USAGE_VERTEX); + + private boolean scissorEnabled = false; + private int scissorX, scissorY, scissorW, scissorH; + private long currentOffset = 0; + private int vertexCount = 0; + + public void addOutline(float x, float y, float width, float height, float radius, float outlineWidth, Color color) { + addOutline(x, y, width, height, radius, radius, radius, radius, outlineWidth, color); + } + + public void addOutline(float x, float y, float width, float height, float radiusTopLeft, float radiusTopRight, float radiusBottomRight, float radiusBottomLeft, float outlineWidth, Color color) { + addOutlineGradient(x, y, width, height, radiusTopLeft, radiusTopRight, radiusBottomRight, radiusBottomLeft, outlineWidth, color, color, color, color + ); + } + + public void addVerticalGradient(float x, float y, float width, float height, float radius, float outlineWidth, Color top, Color bottom) { + addVerticalGradient(x, y, width, height, radius, radius, radius, radius, outlineWidth, top, bottom); + } + + public void addVerticalGradient(float x, float y, float width, float height, float radiusTopLeft, float radiusTopRight, float radiusBottomRight, float radiusBottomLeft, float outlineWidth, Color top, Color bottom) { + addOutlineGradient(x, y, width, height, radiusTopLeft, radiusTopRight, radiusBottomRight, radiusBottomLeft, outlineWidth, top, bottom, bottom, top); + } + + public void addHorizontalGradient(float x, float y, float width, float height, float radius, float outlineWidth, Color left, Color right) { + addHorizontalGradient(x, y, width, height, radius, radius, radius, radius, outlineWidth, left, right); + } + + public void addHorizontalGradient(float x, float y, float width, float height, float radiusTopLeft, float radiusTopRight, float radiusBottomRight, float radiusBottomLeft, float outlineWidth, Color left, Color right) { + addOutlineGradient(x, y, width, height, radiusTopLeft, radiusTopRight, radiusBottomRight, radiusBottomLeft, outlineWidth, left, left, right, right); + } + + /** + * 颜色顺序对应四个角顶点:左上、左下、右下、右上 (TL, BL, BR, TR) + */ + public void addOutlineGradient(float x, float y, float width, float height, float radiusTopLeft, float radiusTopRight, float radiusBottomRight, float radiusBottomLeft, float outlineWidth, Color colorTopLeft, Color colorBottomLeft, Color colorBottomRight, Color colorTopRight) { + if (outlineWidth <= 0.0f) return; + + buffer.tryMap(); + + float halfOutline = outlineWidth * 0.5f; + float x2 = x + width; + float y2 = y + height; + float outerX1 = x - halfOutline; + float outerY1 = y - halfOutline; + float outerX2 = x2 + halfOutline; + float outerY2 = y2 + halfOutline; + int argbTopLeft = ARGB.toABGR(colorTopLeft.getRGB()); + int argbBottomLeft = ARGB.toABGR(colorBottomLeft.getRGB()); + int argbBottomRight = ARGB.toABGR(colorBottomRight.getRGB()); + int argbTopRight = ARGB.toABGR(colorTopRight.getRGB()); + + addVertex(outerX1, outerY1, x, y, x2, y2, radiusTopLeft, radiusTopRight, radiusBottomRight, radiusBottomLeft, outlineWidth, argbTopLeft); + addVertex(outerX1, outerY2, x, y, x2, y2, radiusTopLeft, radiusTopRight, radiusBottomRight, radiusBottomLeft, outlineWidth, argbBottomLeft); + addVertex(outerX2, outerY2, x, y, x2, y2, radiusTopLeft, radiusTopRight, radiusBottomRight, radiusBottomLeft, outlineWidth, argbBottomRight); + addVertex(outerX2, outerY1, x, y, x2, y2, radiusTopLeft, radiusTopRight, radiusBottomRight, radiusBottomLeft, outlineWidth, argbTopRight); + } + + private void addVertex(float vx, float vy, float rx1, float ry1, float rx2, float ry2, float r1, float r2, float r3, float r4, float outlineWidth, int color) { + long baseAddr = MemoryUtil.memAddress(buffer.getMappedBuffer()); + long p = baseAddr + currentOffset; + + MemoryUtil.memPutFloat(p, vx); + MemoryUtil.memPutFloat(p + 4, vy); + MemoryUtil.memPutFloat(p + 8, 0.0f); + MemoryUtil.memPutInt(p + 12, color); + MemoryUtil.memPutFloat(p + 16, rx1); + MemoryUtil.memPutFloat(p + 20, ry1); + MemoryUtil.memPutFloat(p + 24, rx2); + MemoryUtil.memPutFloat(p + 28, ry2); + MemoryUtil.memPutFloat(p + 32, r1); + MemoryUtil.memPutFloat(p + 36, r2); + MemoryUtil.memPutFloat(p + 40, r3); + MemoryUtil.memPutFloat(p + 44, r4); + MemoryUtil.memPutFloat(p + 48, outlineWidth); + currentOffset += STRIDE; + vertexCount++; + } + + public void setScissor(int x, int y, int width, int height) { + scissorEnabled = true; + scissorX = x; + scissorY = y; + scissorW = width; + scissorH = height; + } + + public void clearScissor() { + scissorEnabled = false; + } + + @Override + public void draw() { + if (vertexCount == 0) return; + if (buffer.isMapped()) buffer.unmap(); + + LuminRenderSystem.QuadRenderingInfo info = LuminRenderSystem.prepareQuadRendering(vertexCount); + if (info == null || info.colorView() == null) return; + + try (RenderPass pass = RenderSystem.getDevice().createCommandEncoder().createRenderPass( + () -> "Round Rect Outline Draw", info.colorView(), OptionalInt.empty(), + info.depthView(), OptionalDouble.empty()) + ) { + pass.setPipeline(LuminRenderPipelines.ROUND_RECT_OUTLINE); + if (scissorEnabled) pass.enableScissor(scissorX, scissorY, scissorW, scissorH); + RenderSystem.bindDefaultUniforms(pass); + pass.setUniform("DynamicTransforms", info.dynamicUniforms()); + pass.setVertexBuffer(0, buffer.getGpuBuffer()); + pass.setIndexBuffer(info.ibo(), info.autoIndices().type()); + pass.drawIndexed(0, 0, info.indexCount(), 1); + } + } + + @Override + public void clear() { + if (vertexCount > 0) { + if (buffer.isMapped()) buffer.unmap(); + buffer.rotate(); + } + vertexCount = 0; + currentOffset = 0; + } + + @Override + public void close() { + buffer.close(); + } + +} diff --git a/common/src/main/java/com/github/epsilon/gui/hudeditor/HudEditorInspector.java b/common/src/main/java/com/github/epsilon/gui/hudeditor/HudEditorInspector.java index bc71fbcb..4a00fbc7 100644 --- a/common/src/main/java/com/github/epsilon/gui/hudeditor/HudEditorInspector.java +++ b/common/src/main/java/com/github/epsilon/gui/hudeditor/HudEditorInspector.java @@ -145,7 +145,7 @@ public void queueRender(GuiGraphicsExtractor graphics, HudModule selectedModule, settingList.layoutRows(settings, viewport, scroll, rowWidth, (_, row, rowBounds) -> { float hover = rowBounds.contains(effectiveMouseX, effectiveMouseY) ? 1.0f : 0.0f; - row.render(graphics, contentBuffer.roundRectRenderer(), contentBuffer.rectRenderer(), contentBuffer.textRenderer(), rowBounds, hover, effectiveMouseX, effectiveMouseY, partialTick); + row.render(graphics, contentBuffer.roundRectRenderer(), contentBuffer.roundRectOutlineRenderer(), contentBuffer.rectRenderer(), contentBuffer.textRenderer(), rowBounds, hover, effectiveMouseX, effectiveMouseY, partialTick); }); flushChrome(); diff --git a/common/src/main/java/com/github/epsilon/gui/panel/MD3Theme.java b/common/src/main/java/com/github/epsilon/gui/panel/MD3Theme.java index 6b98860e..64b80af8 100644 --- a/common/src/main/java/com/github/epsilon/gui/panel/MD3Theme.java +++ b/common/src/main/java/com/github/epsilon/gui/panel/MD3Theme.java @@ -65,8 +65,13 @@ public class MD3Theme { public static final float CONTROL_HEIGHT = 18.0f; public static final float CONTROL_RADIUS = 7.0f; public static final float COMPACT_CHIP_HEIGHT = 16.0f; - public static final float SWITCH_WIDTH = 30.0f; + public static final float SWITCH_WIDTH = 26.0f; public static final float SWITCH_HEIGHT = 16.0f; + public static final float SWITCH_HANDLE_SIZE_OFF = 8.0f; + public static final float SWITCH_HANDLE_SIZE_ON = 12.0f; + public static final float SWITCH_HANDLE_INSET_OFF = 4.0f; + public static final float SWITCH_HANDLE_INSET_ON = 2.0f; + public static final float SWITCH_STATE_LAYER_SIZE = 20.0f; private MD3Theme() { } @@ -176,6 +181,25 @@ public static Color segmentedControlInactiveLabel() { return isLightTheme() ? TEXT_SECONDARY : TEXT_MUTED; } + public static Color switchTrack(float toggleProgress) { + return lerp(SURFACE_CONTAINER_HIGHEST, PRIMARY, toggleProgress); + } + + public static Color switchKnob(float toggleProgress) { + return lerp(OUTLINE, ON_PRIMARY, toggleProgress); + } + + public static Color switchTrackOutline(float toggleProgress, float hoverProgress) { + float inactive = 1.0f - Mth.clamp(toggleProgress, 0.0f, 1.0f); + float hoverMix = Mth.clamp(hoverProgress * 0.35f, 0.0f, 1.0f); + Color base = lerp(OUTLINE, TEXT_PRIMARY, hoverMix); + return withAlpha(base, (int) (inactive * (isLightTheme() ? 188 : 168))); + } + + public static float switchTrackOutlineWidth(float toggleProgress) { + return 1.0f + (1.0f - Mth.clamp(toggleProgress, 0.0f, 1.0f)) * 0.1f; + } + private record ThemePalette( Color shadow, Color surface, diff --git a/common/src/main/java/com/github/epsilon/gui/panel/PanelScreen.java b/common/src/main/java/com/github/epsilon/gui/panel/PanelScreen.java index 6992e7dd..7b5690fb 100644 --- a/common/src/main/java/com/github/epsilon/gui/panel/PanelScreen.java +++ b/common/src/main/java/com/github/epsilon/gui/panel/PanelScreen.java @@ -2,10 +2,7 @@ import com.github.epsilon.assets.holders.TranslateHolder; import com.github.epsilon.graphics.LuminRenderSystem; -import com.github.epsilon.graphics.renderers.RectRenderer; -import com.github.epsilon.graphics.renderers.RoundRectRenderer; -import com.github.epsilon.graphics.renderers.ShadowRenderer; -import com.github.epsilon.graphics.renderers.TextRenderer; +import com.github.epsilon.graphics.renderers.*; import com.github.epsilon.gui.panel.dsl.PanelRenderBatch; import com.github.epsilon.gui.panel.input.PanelInputRouter; import com.github.epsilon.gui.panel.panel.CategoryRailPanel; @@ -39,8 +36,9 @@ public class PanelScreen extends Screen { private final TextRenderer textRenderer = new TextRenderer(); private final RectRenderer rectRenderer = new RectRenderer(); private final RoundRectRenderer roundRectRenderer = new RoundRectRenderer(); + private final RoundRectOutlineRenderer roundRectOutlineRenderer = new RoundRectOutlineRenderer(); private final ShadowRenderer shadowRenderer = new ShadowRenderer(); - private final PanelRenderBatch renderBatch = new PanelRenderBatch(shadowRenderer, roundRectRenderer, rectRenderer, textRenderer); + private final PanelRenderBatch renderBatch = new PanelRenderBatch(shadowRenderer, roundRectRenderer, roundRectOutlineRenderer, rectRenderer, textRenderer); private final PanelPopupHost popupHost = new PanelPopupHost(); private final PanelInputRouter inputRouter = new PanelInputRouter(); private final CategoryRailPanel categoryRailPanel = new CategoryRailPanel(state, rectRenderer, roundRectRenderer, textRenderer); diff --git a/common/src/main/java/com/github/epsilon/gui/panel/component/PanelElements.java b/common/src/main/java/com/github/epsilon/gui/panel/component/PanelElements.java index ffaa066f..9947c5ed 100644 --- a/common/src/main/java/com/github/epsilon/gui/panel/component/PanelElements.java +++ b/common/src/main/java/com/github/epsilon/gui/panel/component/PanelElements.java @@ -1,6 +1,7 @@ package com.github.epsilon.gui.panel.component; import com.github.epsilon.graphics.renderers.RectRenderer; +import com.github.epsilon.graphics.renderers.RoundRectOutlineRenderer; import com.github.epsilon.graphics.renderers.RoundRectRenderer; import com.github.epsilon.graphics.renderers.TextRenderer; import com.github.epsilon.graphics.text.ttf.TtfFontLoader; @@ -59,17 +60,32 @@ public static PanelLayout.Rect compactFieldBounds(PanelLayout.Rect bounds, float } public static void drawSwitch(RoundRectRenderer roundRectRenderer, PanelLayout.Rect rect, float toggleProgress, float hoverProgress) { - Color track = MD3Theme.lerp(MD3Theme.SURFACE_CONTAINER_HIGHEST, MD3Theme.PRIMARY, toggleProgress); - Color knob = MD3Theme.lerp(MD3Theme.OUTLINE, MD3Theme.ON_PRIMARY, toggleProgress); - - float knobSize = 8.0f + 3.0f * toggleProgress; - float knobTravel = rect.width() - 10.0f - knobSize; - float knobX = rect.x() + 5.0f + knobTravel * toggleProgress; + drawSwitch(roundRectRenderer, null, rect, toggleProgress, hoverProgress); + } + + public static void drawSwitch(RoundRectRenderer roundRectRenderer, RoundRectOutlineRenderer roundRectOutlineRenderer, PanelLayout.Rect rect, float toggleProgress, float hoverProgress) { + Color track = MD3Theme.switchTrack(toggleProgress); + Color knob = MD3Theme.switchKnob(toggleProgress); + Color outline = MD3Theme.switchTrackOutline(toggleProgress, hoverProgress); + float clampedToggle = Math.clamp(toggleProgress, 0.0f, 1.0f); + float knobSize = MD3Theme.SWITCH_HANDLE_SIZE_OFF + + (MD3Theme.SWITCH_HANDLE_SIZE_ON - MD3Theme.SWITCH_HANDLE_SIZE_OFF) * clampedToggle; + float knobStartX = rect.x() + MD3Theme.SWITCH_HANDLE_INSET_OFF; + float knobEndX = rect.right() - MD3Theme.SWITCH_HANDLE_INSET_ON - knobSize; + float knobX = knobStartX + (knobEndX - knobStartX) * clampedToggle; float knobY = rect.centerY() - knobSize / 2.0f; roundRectRenderer.addRoundRect(rect.x(), rect.y(), rect.width(), rect.height(), rect.height() / 2.0f, track); + if (outline.getAlpha() > 0 && roundRectOutlineRenderer != null) { + roundRectOutlineRenderer.addOutline( + rect.x(), rect.y(), rect.width(), rect.height(), + rect.height() / 2.0f, + MD3Theme.switchTrackOutlineWidth(toggleProgress), + outline + ); + } if (hoverProgress > 0.02f) { - float haloSize = 16.0f; + float haloSize = MD3Theme.SWITCH_STATE_LAYER_SIZE; float haloX = knobX + knobSize / 2.0f - haloSize / 2.0f; float haloY = rect.centerY() - haloSize / 2.0f; roundRectRenderer.addRoundRect(haloX, haloY, haloSize, haloSize, haloSize / 2.0f, diff --git a/common/src/main/java/com/github/epsilon/gui/panel/component/SettingRow.java b/common/src/main/java/com/github/epsilon/gui/panel/component/SettingRow.java index 452a61ea..092c4353 100644 --- a/common/src/main/java/com/github/epsilon/gui/panel/component/SettingRow.java +++ b/common/src/main/java/com/github/epsilon/gui/panel/component/SettingRow.java @@ -1,6 +1,7 @@ package com.github.epsilon.gui.panel.component; import com.github.epsilon.graphics.renderers.RectRenderer; +import com.github.epsilon.graphics.renderers.RoundRectOutlineRenderer; import com.github.epsilon.graphics.renderers.RoundRectRenderer; import com.github.epsilon.graphics.renderers.TextRenderer; import com.github.epsilon.gui.panel.PanelLayout; @@ -30,9 +31,9 @@ public float getHeight() { return 28.0f; } - public void render(GuiGraphicsExtractor GuiGraphicsExtractor, RoundRectRenderer roundRectRenderer, RectRenderer rectRenderer, TextRenderer textRenderer, PanelLayout.Rect bounds, float hoverProgress, int mouseX, int mouseY, float partialTick) { + public void render(GuiGraphicsExtractor GuiGraphicsExtractor, RoundRectRenderer roundRectRenderer, RoundRectOutlineRenderer roundRectOutlineRenderer, RectRenderer rectRenderer, TextRenderer textRenderer, PanelLayout.Rect bounds, float hoverProgress, int mouseX, int mouseY, float partialTick) { PanelUiTree tree = PanelUiTree.build(scope -> buildUi(scope, GuiGraphicsExtractor, textRenderer, bounds, hoverProgress, mouseX, mouseY, partialTick)); - PanelUiCompiler.render(tree, roundRectRenderer, rectRenderer, textRenderer); + PanelUiCompiler.render(tree, null, roundRectRenderer, roundRectOutlineRenderer, rectRenderer, textRenderer); } public void buildUi(PanelUiTree.Scope scope, GuiGraphicsExtractor guiGraphics, TextRenderer textRenderer, diff --git a/common/src/main/java/com/github/epsilon/gui/panel/component/setting/BoolSettingRow.java b/common/src/main/java/com/github/epsilon/gui/panel/component/setting/BoolSettingRow.java index a9498744..e61267aa 100644 --- a/common/src/main/java/com/github/epsilon/gui/panel/component/setting/BoolSettingRow.java +++ b/common/src/main/java/com/github/epsilon/gui/panel/component/setting/BoolSettingRow.java @@ -33,7 +33,7 @@ public void buildUi(PanelUiTree.Scope scope, GuiGraphicsExtractor guiGraphics, T float toggleProgress = scope.animate(toggleAnimation, setting.getValue()); scope.roundRect(bounds.x(), bounds.y(), bounds.width(), bounds.height(), MD3Theme.CARD_RADIUS, MD3Theme.rowSurface(animatedHover)); - scope.text(setting.getDisplayName(), bounds.x() + MD3Theme.ROW_CONTENT_INSET, labelY, labelScale, MD3Theme.TEXT_PRIMARY); + scope.text(setting.getDisplayName(), PanelElements.rowLabelX(bounds), labelY, labelScale, MD3Theme.TEXT_PRIMARY); scope.toggle(getSwitchBounds(bounds), toggleProgress, animatedHover); } diff --git a/common/src/main/java/com/github/epsilon/gui/panel/dsl/PanelRenderBatch.java b/common/src/main/java/com/github/epsilon/gui/panel/dsl/PanelRenderBatch.java index bbddd12b..d87ba90a 100644 --- a/common/src/main/java/com/github/epsilon/gui/panel/dsl/PanelRenderBatch.java +++ b/common/src/main/java/com/github/epsilon/gui/panel/dsl/PanelRenderBatch.java @@ -1,9 +1,6 @@ package com.github.epsilon.gui.panel.dsl; -import com.github.epsilon.graphics.renderers.RectRenderer; -import com.github.epsilon.graphics.renderers.RoundRectRenderer; -import com.github.epsilon.graphics.renderers.ShadowRenderer; -import com.github.epsilon.graphics.renderers.TextRenderer; +import com.github.epsilon.graphics.renderers.*; /** * 面板 UI 的通用 renderer 批次封装。 @@ -12,10 +9,11 @@ * {@link PanelUiCompiler} 的编译输出,并在统一阶段执行 flush 或 clear。 */ public record PanelRenderBatch(ShadowRenderer shadowRenderer, RoundRectRenderer roundRectRenderer, - RectRenderer rectRenderer, TextRenderer textRenderer) { + RoundRectOutlineRenderer roundRectOutlineRenderer, RectRenderer rectRenderer, + TextRenderer textRenderer) { public PanelRenderBatch() { - this(new ShadowRenderer(), new RoundRectRenderer(), new RectRenderer(), new TextRenderer()); + this(new ShadowRenderer(), new RoundRectRenderer(), new RoundRectOutlineRenderer(), new RectRenderer(), new TextRenderer()); } /** @@ -24,7 +22,7 @@ public PanelRenderBatch() { * @param tree 待编译的 UI 树 */ public void render(PanelUiTree tree) { - PanelUiCompiler.render(tree, shadowRenderer, roundRectRenderer, rectRenderer, textRenderer); + PanelUiCompiler.render(tree, shadowRenderer, roundRectRenderer, roundRectOutlineRenderer, rectRenderer, textRenderer); } /** @@ -35,6 +33,7 @@ public void render(PanelUiTree tree) { public void flush() { shadowRenderer.draw(); roundRectRenderer.draw(); + roundRectOutlineRenderer.draw(); rectRenderer.draw(); textRenderer.draw(); } @@ -45,6 +44,7 @@ public void flush() { public void clear() { shadowRenderer.clear(); roundRectRenderer.clear(); + roundRectOutlineRenderer.clear(); rectRenderer.clear(); textRenderer.clear(); } @@ -56,5 +56,5 @@ public void flushAndClear() { flush(); clear(); } -} +} diff --git a/common/src/main/java/com/github/epsilon/gui/panel/dsl/PanelUiCompiler.java b/common/src/main/java/com/github/epsilon/gui/panel/dsl/PanelUiCompiler.java index ca86e914..1a6d2952 100644 --- a/common/src/main/java/com/github/epsilon/gui/panel/dsl/PanelUiCompiler.java +++ b/common/src/main/java/com/github/epsilon/gui/panel/dsl/PanelUiCompiler.java @@ -1,12 +1,10 @@ package com.github.epsilon.gui.panel.dsl; -import com.github.epsilon.graphics.renderers.RectRenderer; -import com.github.epsilon.graphics.renderers.RoundRectRenderer; -import com.github.epsilon.graphics.renderers.ShadowRenderer; -import com.github.epsilon.graphics.renderers.TextRenderer; +import com.github.epsilon.graphics.renderers.*; import com.github.epsilon.graphics.text.ttf.TtfFontLoader; import com.github.epsilon.gui.panel.MD3Theme; import com.github.epsilon.gui.panel.PanelLayout; +import com.github.epsilon.gui.panel.component.PanelElements; import com.github.epsilon.gui.panel.utils.PanelContentBuffer; import java.awt.*; @@ -31,7 +29,7 @@ private PanelUiCompiler() { * @param textRenderer 文本 renderer */ public static void render(PanelUiTree tree, RoundRectRenderer roundRectRenderer, RectRenderer rectRenderer, TextRenderer textRenderer) { - render(tree, null, roundRectRenderer, rectRenderer, textRenderer); + render(tree, null, roundRectRenderer, null, rectRenderer, textRenderer); } /** @@ -47,7 +45,12 @@ public static void render(PanelUiTree tree, RoundRectRenderer roundRectRenderer, * @param textRenderer 文本 renderer */ public static void render(PanelUiTree tree, ShadowRenderer shadowRenderer, RoundRectRenderer roundRectRenderer, RectRenderer rectRenderer, TextRenderer textRenderer) { - renderNodes(tree.nodes(), new RenderTarget(shadowRenderer, roundRectRenderer, rectRenderer, textRenderer)); + render(tree, shadowRenderer, roundRectRenderer, null, rectRenderer, textRenderer); + } + + public static void render(PanelUiTree tree, ShadowRenderer shadowRenderer, RoundRectRenderer roundRectRenderer, + RoundRectOutlineRenderer roundRectOutlineRenderer, RectRenderer rectRenderer, TextRenderer textRenderer) { + renderNodes(tree.nodes(), new RenderTarget(shadowRenderer, roundRectRenderer, roundRectOutlineRenderer, rectRenderer, textRenderer)); } private static void renderNodes(List nodes, RenderTarget target) { @@ -256,21 +259,7 @@ private static void renderSlider(RenderTarget target, PanelLayout.Rect bounds, f } private static void renderSwitch(RenderTarget target, PanelLayout.Rect bounds, float toggleProgress, float hoverProgress) { - Color track = MD3Theme.lerp(MD3Theme.SURFACE_CONTAINER_HIGHEST, MD3Theme.PRIMARY, toggleProgress); - Color knob = MD3Theme.lerp(MD3Theme.OUTLINE, MD3Theme.ON_PRIMARY, toggleProgress); - float knobSize = 8.0f + 3.0f * toggleProgress; - float knobTravel = bounds.width() - 10.0f - knobSize; - float knobX = bounds.x() + 5.0f + knobTravel * toggleProgress; - float knobY = bounds.centerY() - knobSize / 2.0f; - target.roundRectRenderer().addRoundRect(bounds.x(), bounds.y(), bounds.width(), bounds.height(), bounds.height() / 2.0f, track); - if (hoverProgress > 0.02f) { - float haloSize = 16.0f; - float haloX = knobX + knobSize / 2.0f - haloSize / 2.0f; - float haloY = bounds.centerY() - haloSize / 2.0f; - target.roundRectRenderer().addRoundRect(haloX, haloY, haloSize, haloSize, haloSize / 2.0f, - MD3Theme.stateLayer(MD3Theme.TEXT_PRIMARY, hoverProgress, 18)); - } - target.roundRectRenderer().addRoundRect(knobX, knobY, knobSize, knobSize, knobSize / 2.0f, knob); + PanelElements.drawSwitch(target.roundRectRenderer(), target.roundRectOutlineRenderer(), bounds, toggleProgress, hoverProgress); } private static void renderFilledField(RenderTarget target, PanelLayout.Rect bounds, boolean focused, float hoverProgress) { @@ -327,15 +316,14 @@ private static void renderInput(RenderTarget target, PanelUiTree.InputElement el } private record RenderTarget(ShadowRenderer shadowRenderer, RoundRectRenderer roundRectRenderer, - RectRenderer rectRenderer, TextRenderer textRenderer, - PanelContentBuffer buffer) { - private RenderTarget(ShadowRenderer shadowRenderer, RoundRectRenderer roundRectRenderer, - RectRenderer rectRenderer, TextRenderer textRenderer) { - this(shadowRenderer, roundRectRenderer, rectRenderer, textRenderer, null); + RoundRectOutlineRenderer roundRectOutlineRenderer, RectRenderer rectRenderer, + TextRenderer textRenderer, PanelContentBuffer buffer) { + private RenderTarget(ShadowRenderer shadowRenderer, RoundRectRenderer roundRectRenderer, RoundRectOutlineRenderer roundRectOutlineRenderer, RectRenderer rectRenderer, TextRenderer textRenderer) { + this(shadowRenderer, roundRectRenderer, roundRectOutlineRenderer, rectRenderer, textRenderer, null); } private static RenderTarget forContentBuffer(PanelContentBuffer buffer) { - return new RenderTarget(buffer.shadowRenderer(), buffer.roundRectRenderer(), buffer.rectRenderer(), buffer.textRenderer(), buffer); + return new RenderTarget(buffer.shadowRenderer(), buffer.roundRectRenderer(), buffer.roundRectOutlineRenderer(), buffer.rectRenderer(), buffer.textRenderer(), buffer); } } diff --git a/common/src/main/java/com/github/epsilon/gui/panel/panel/ModuleDetailPanel.java b/common/src/main/java/com/github/epsilon/gui/panel/panel/ModuleDetailPanel.java index e8e08843..a34561a4 100644 --- a/common/src/main/java/com/github/epsilon/gui/panel/panel/ModuleDetailPanel.java +++ b/common/src/main/java/com/github/epsilon/gui/panel/panel/ModuleDetailPanel.java @@ -125,7 +125,7 @@ public void render(GuiGraphicsExtractor GuiGraphicsExtractor, PanelLayout.Rect b } Animation hoverAnimation = hoverAnimations.computeIfAbsent(setting, ignored -> new Animation(Easing.EASE_OUT_CUBIC, 120L)); hoverAnimation.run(rowBounds.contains(effectiveMouseX, effectiveMouseY) ? 1.0f : 0.0f); - row.render(GuiGraphicsExtractor, contentBuffer.roundRectRenderer(), contentBuffer.rectRenderer(), contentBuffer.textRenderer(), rowBounds, hoverAnimation.getValue(), effectiveMouseX, effectiveMouseY, partialTick); + row.render(GuiGraphicsExtractor, contentBuffer.roundRectRenderer(), contentBuffer.roundRectOutlineRenderer(), contentBuffer.rectRenderer(), contentBuffer.textRenderer(), rowBounds, hoverAnimation.getValue(), effectiveMouseX, effectiveMouseY, partialTick); contentState.noteAnimation(!hoverAnimation.isFinished() || row.hasActiveAnimation()); }); diff --git a/common/src/main/java/com/github/epsilon/gui/panel/utils/PanelContentBuffer.java b/common/src/main/java/com/github/epsilon/gui/panel/utils/PanelContentBuffer.java index 819e7fab..3e4171a9 100644 --- a/common/src/main/java/com/github/epsilon/gui/panel/utils/PanelContentBuffer.java +++ b/common/src/main/java/com/github/epsilon/gui/panel/utils/PanelContentBuffer.java @@ -1,9 +1,6 @@ package com.github.epsilon.gui.panel.utils; -import com.github.epsilon.graphics.renderers.RectRenderer; -import com.github.epsilon.graphics.renderers.RoundRectRenderer; -import com.github.epsilon.graphics.renderers.ShadowRenderer; -import com.github.epsilon.graphics.renderers.TextRenderer; +import com.github.epsilon.graphics.renderers.*; import com.github.epsilon.graphics.text.ttf.TtfFontLoader; import com.github.epsilon.gui.panel.PanelLayout; import net.minecraft.client.Minecraft; @@ -21,6 +18,7 @@ public class PanelContentBuffer { private final RoundRectRenderer roundRectRenderer = new RoundRectRenderer(); + private final RoundRectOutlineRenderer roundRectOutlineRenderer = new RoundRectOutlineRenderer(); private final RectRenderer rectRenderer = new RectRenderer(); private final ShadowRenderer shadowRenderer = new ShadowRenderer(); private final TextRenderer textRenderer = new TextRenderer(); @@ -40,6 +38,10 @@ public RectRenderer rectRenderer() { return rectRenderer; } + public RoundRectOutlineRenderer roundRectOutlineRenderer() { + return roundRectOutlineRenderer; + } + public ShadowRenderer shadowRenderer() { return shadowRenderer; } @@ -71,6 +73,7 @@ public void addMarqueeText(MarqueeTextDraw draw) { private void clearContent() { shadowRenderer.clear(); roundRectRenderer.clear(); + roundRectOutlineRenderer.clear(); rectRenderer.clear(); textRenderer.clear(); } @@ -87,7 +90,7 @@ private void clearContent() { * @param contentHeight 内容总高度 */ public void queueViewport(PanelLayout.Rect viewport, int guiHeight, float scroll, float maxScroll, float contentHeight) { - PanelScissor.apply(viewport, rectRenderer, roundRectRenderer, shadowRenderer, textRenderer, guiHeight); + PanelScissor.apply(viewport, rectRenderer, roundRectRenderer, roundRectOutlineRenderer, shadowRenderer, textRenderer, guiHeight); scrollBarRenderer.clear(); ScrollBarUtils.draw(scrollBarRenderer, viewport, scroll, maxScroll, contentHeight); pendingViewport = viewport; @@ -104,9 +107,10 @@ public void flush() { } shadowRenderer.draw(); roundRectRenderer.draw(); + roundRectOutlineRenderer.draw(); rectRenderer.draw(); textRenderer.draw(); - PanelScissor.clear(rectRenderer, roundRectRenderer, shadowRenderer, textRenderer); + PanelScissor.clear(rectRenderer, roundRectRenderer, roundRectOutlineRenderer, shadowRenderer, textRenderer); flushMarqueeTexts(); scrollBarRenderer.draw(); scrollBarRenderer.clear(); diff --git a/common/src/main/java/com/github/epsilon/gui/panel/utils/PanelScissor.java b/common/src/main/java/com/github/epsilon/gui/panel/utils/PanelScissor.java index adf5aab6..4c03954b 100644 --- a/common/src/main/java/com/github/epsilon/gui/panel/utils/PanelScissor.java +++ b/common/src/main/java/com/github/epsilon/gui/panel/utils/PanelScissor.java @@ -1,9 +1,6 @@ package com.github.epsilon.gui.panel.utils; -import com.github.epsilon.graphics.renderers.RectRenderer; -import com.github.epsilon.graphics.renderers.RoundRectRenderer; -import com.github.epsilon.graphics.renderers.ShadowRenderer; -import com.github.epsilon.graphics.renderers.TextRenderer; +import com.github.epsilon.graphics.renderers.*; import com.github.epsilon.gui.panel.PanelLayout; import net.minecraft.client.Minecraft; @@ -12,7 +9,7 @@ public class PanelScissor { private PanelScissor() { } - public static void apply(PanelLayout.Rect rect, RectRenderer rectRenderer, RoundRectRenderer roundRectRenderer, ShadowRenderer shadowRenderer, TextRenderer textRenderer, int guiHeight) { + public static void apply(PanelLayout.Rect rect, RectRenderer rectRenderer, RoundRectRenderer roundRectRenderer, RoundRectOutlineRenderer roundRectOutlineRenderer, ShadowRenderer shadowRenderer, TextRenderer textRenderer, int guiHeight) { int scale = Minecraft.getInstance().getWindow().getGuiScale(); int x = Math.round(rect.x() * scale); int y = Math.round((guiHeight - rect.bottom()) * scale); @@ -20,13 +17,15 @@ public static void apply(PanelLayout.Rect rect, RectRenderer rectRenderer, Round int height = Math.round(rect.height() * scale); rectRenderer.setScissor(x, y, width, height); roundRectRenderer.setScissor(x, y, width, height); + roundRectOutlineRenderer.setScissor(x, y, width, height); shadowRenderer.setScissor(x, y, width, height); textRenderer.setScissor(x, y, width, height); } - public static void clear(RectRenderer rectRenderer, RoundRectRenderer roundRectRenderer, ShadowRenderer shadowRenderer, TextRenderer textRenderer) { + public static void clear(RectRenderer rectRenderer, RoundRectRenderer roundRectRenderer, RoundRectOutlineRenderer roundRectOutlineRenderer, ShadowRenderer shadowRenderer, TextRenderer textRenderer) { rectRenderer.clearScissor(); roundRectRenderer.clearScissor(); + roundRectOutlineRenderer.clearScissor(); shadowRenderer.clearScissor(); textRenderer.clearScissor(); } diff --git a/common/src/main/java/com/github/epsilon/gui/screen/MainMenuScreen.java b/common/src/main/java/com/github/epsilon/gui/screen/MainMenuScreen.java index 595bfe7c..e00c58c4 100644 --- a/common/src/main/java/com/github/epsilon/gui/screen/MainMenuScreen.java +++ b/common/src/main/java/com/github/epsilon/gui/screen/MainMenuScreen.java @@ -156,11 +156,11 @@ private void renderEntry(MenuEntry entry, int index, int mouseX, int mouseY, flo Color lineBase = applyAlpha(MD3Theme.TEXT_MUTED, 0.70f * appear); - Color lineHover = applyAlpha(entry.title.equals("Quit") ? MD3Theme.ERROR : MD3Theme.PRIMARY, 0.98f * appear); + Color lineHover = applyAlpha(MD3Theme.PRIMARY, 0.98f * appear); Color labelColor = MD3Theme.lerp( applyAlpha(MD3Theme.TEXT_PRIMARY, 0.94f * appear), - applyAlpha(entry.title.equals("Quit") ? MD3Theme.ON_TERTIARY_CONTAINER : MD3Theme.ON_PRIMARY_CONTAINER, 0.98f * appear), + applyAlpha(MD3Theme.ON_PRIMARY_CONTAINER, 0.98f * appear), hover * 0.68f ); diff --git a/common/src/main/java/com/github/epsilon/utils/render/esp/CircleESP.java b/common/src/main/java/com/github/epsilon/utils/render/esp/CircleESP.java index 5f5a36eb..5821d3f5 100644 --- a/common/src/main/java/com/github/epsilon/utils/render/esp/CircleESP.java +++ b/common/src/main/java/com/github/epsilon/utils/render/esp/CircleESP.java @@ -15,7 +15,6 @@ import net.minecraft.world.entity.LivingEntity; import org.joml.Matrix4f; import org.joml.Vector2f; -import org.joml.Vector3f; import java.awt.*; @@ -70,10 +69,10 @@ public static void render(PoseStack poseStack, LivingEntity target, float radius Vector2f nextPoint = getPoint(nextAngle, radius); Vector2f linePoint = getPoint(radAngle, radius); - Vector3f normal = getNormal(radAngle); + Vector2f normal = getNormal(radAngle); - lineBuffer.addVertex(entry, linePoint.x, 0, linePoint.y).setColor(lineColor.getRed(), lineColor.getGreen(), lineColor.getBlue(), Math.round(lineColor.getAlpha() * alpha)).setNormal(entry, normal.x, normal.y, normal.z).setLineWidth(2f); - lineBuffer.addVertex(entry, nextPoint.x, 0, nextPoint.y).setColor(lineColor.getRed(), lineColor.getGreen(), lineColor.getBlue(), Math.round(lineColor.getAlpha() * alpha)).setNormal(entry, normal.x, normal.y, normal.z).setLineWidth(2f); + lineBuffer.addVertex(entry, linePoint.x, 0f, linePoint.y).setColor(lineColor.getRed(), lineColor.getGreen(), lineColor.getBlue(), Math.round(lineColor.getAlpha() * alpha)).setNormal(entry, normal.x, 0f, normal.y).setLineWidth(2f); + lineBuffer.addVertex(entry, nextPoint.x, 0f, nextPoint.y).setColor(lineColor.getRed(), lineColor.getGreen(), lineColor.getBlue(), Math.round(lineColor.getAlpha() * alpha)).setNormal(entry, normal.x, 0f, normal.y).setLineWidth(2f); } Render3DUtils.LINES.draw(lineBuffer.buildOrThrow()); @@ -81,12 +80,12 @@ public static void render(PoseStack poseStack, LivingEntity target, float radius poseStack.popPose(); } - private static Vector3f getNormal(float radAngle) { - return new Vector3f((float) -Math.cos(radAngle), 0.0f, (float) -Math.sin(radAngle)); - } - private static Vector2f getPoint(float radAngle, float radius) { return new Vector2f((float) (-Math.sin(radAngle) * radius), (float) (Math.cos(radAngle) * radius)); } + private static Vector2f getNormal(float radAngle) { + return new Vector2f((float) -Math.cos(radAngle), (float) -Math.sin(radAngle)); + } + } diff --git a/common/src/main/resources/assets/epsilon/shaders/round_rectangle_outline.fsh b/common/src/main/resources/assets/epsilon/shaders/round_rectangle_outline.fsh new file mode 100644 index 00000000..9c8147fc --- /dev/null +++ b/common/src/main/resources/assets/epsilon/shaders/round_rectangle_outline.fsh @@ -0,0 +1,35 @@ +#version 410 core + +in vec2 f_Position; +in vec4 f_Color; +in vec4 f_InnerRect; +in vec4 f_Radius; +in float f_OutlineWidth; + +layout(location = 0) out vec4 fragColor; + +float roundRectDistance(vec2 position, vec4 innerRect, vec4 radius) { + vec2 halfSize = (innerRect.zw - innerRect.xy) * 0.5; + vec2 center = (innerRect.xy + innerRect.zw) * 0.5; + vec2 p = position - center; + + vec2 s = step(0.0, p); + float rCurrent = mix( + mix(radius.x, radius.w, s.y), + mix(radius.y, radius.z, s.y), + s.x + ); + + vec2 q = abs(p) - halfSize + rCurrent; + return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - rCurrent; +} + +void main() { + float dist = roundRectDistance(f_Position, f_InnerRect, f_Radius); + float halfOutline = max(f_OutlineWidth * 0.5, 0.0); + float delta = max(fwidth(dist), 0.0001); + float alpha = 1.0 - smoothstep(halfOutline - delta, halfOutline + delta, abs(dist)); + + fragColor = vec4(f_Color.rgb, f_Color.a * alpha); + if (fragColor.a < 0.001) discard; +} diff --git a/common/src/main/resources/assets/epsilon/shaders/round_rectangle_outline.vsh b/common/src/main/resources/assets/epsilon/shaders/round_rectangle_outline.vsh new file mode 100644 index 00000000..504836b9 --- /dev/null +++ b/common/src/main/resources/assets/epsilon/shaders/round_rectangle_outline.vsh @@ -0,0 +1,26 @@ +#version 410 core + +#moj_import +#moj_import + +layout(location = 0) in vec3 Position; +layout(location = 1) in vec4 Color; +layout(location = 2) in vec4 InnerRect; +layout(location = 3) in vec4 Radius; +layout(location = 4) in float OutlineWidth; + +out vec2 f_Position; +out vec4 f_Color; +out vec4 f_InnerRect; +out vec4 f_Radius; +out float f_OutlineWidth; + +void main() { + gl_Position = ProjMat * ModelViewMat * vec4(Position, 1.0); + + f_Position = Position.xy; + f_Color = Color; + f_InnerRect = InnerRect; + f_Radius = Radius; + f_OutlineWidth = OutlineWidth; +}