From ab7256dff5d6d37488081ba7a01b36d3ee9ef563 Mon Sep 17 00:00:00 2001 From: DeDiamondPro <67508414+DeDiamondPro@users.noreply.github.com> Date: Sun, 5 Jun 2022 17:43:23 +0200 Subject: refactor (#36) * refactor * fix vig compat * fix nanovg thingy * e * finalize * gui utils package thingy --- .../oneconfig/renderer/RenderManager.java | 755 +++++++++++++++++++++ .../cc/polyfrost/oneconfig/renderer/font/Font.java | 41 ++ .../oneconfig/renderer/font/FontManager.java | 47 ++ .../polyfrost/oneconfig/renderer/font/Fonts.java | 15 + .../oneconfig/renderer/image/ImageLoader.java | 190 ++++++ .../polyfrost/oneconfig/renderer/image/Images.java | 19 + .../polyfrost/oneconfig/renderer/image/SVGs.java | 52 ++ .../oneconfig/renderer/scissor/Scissor.java | 27 + .../oneconfig/renderer/scissor/ScissorManager.java | 69 ++ 9 files changed, 1215 insertions(+) create mode 100644 src/main/java/cc/polyfrost/oneconfig/renderer/RenderManager.java create mode 100644 src/main/java/cc/polyfrost/oneconfig/renderer/font/Font.java create mode 100644 src/main/java/cc/polyfrost/oneconfig/renderer/font/FontManager.java create mode 100644 src/main/java/cc/polyfrost/oneconfig/renderer/font/Fonts.java create mode 100644 src/main/java/cc/polyfrost/oneconfig/renderer/image/ImageLoader.java create mode 100644 src/main/java/cc/polyfrost/oneconfig/renderer/image/Images.java create mode 100644 src/main/java/cc/polyfrost/oneconfig/renderer/image/SVGs.java create mode 100644 src/main/java/cc/polyfrost/oneconfig/renderer/scissor/Scissor.java create mode 100644 src/main/java/cc/polyfrost/oneconfig/renderer/scissor/ScissorManager.java (limited to 'src/main/java/cc/polyfrost/oneconfig/renderer') diff --git a/src/main/java/cc/polyfrost/oneconfig/renderer/RenderManager.java b/src/main/java/cc/polyfrost/oneconfig/renderer/RenderManager.java new file mode 100644 index 0000000..20fc8f6 --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/renderer/RenderManager.java @@ -0,0 +1,755 @@ +package cc.polyfrost.oneconfig.renderer; + +import cc.polyfrost.oneconfig.gui.Colors; +import cc.polyfrost.oneconfig.config.data.InfoType; +import cc.polyfrost.oneconfig.gui.OneConfigGui; +import cc.polyfrost.oneconfig.renderer.font.Font; +import cc.polyfrost.oneconfig.renderer.font.FontManager; +import cc.polyfrost.oneconfig.renderer.font.Fonts; +import cc.polyfrost.oneconfig.renderer.image.ImageLoader; +import cc.polyfrost.oneconfig.renderer.image.Images; +import cc.polyfrost.oneconfig.renderer.image.SVGs; +import cc.polyfrost.oneconfig.utils.InputUtils; +import cc.polyfrost.oneconfig.utils.NetworkUtils; +import gg.essential.universal.UGraphics; +import gg.essential.universal.UMinecraft; +import gg.essential.universal.UResolution; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.shader.Framebuffer; +import org.lwjgl.nanovg.NVGColor; +import org.lwjgl.nanovg.NVGPaint; +import org.lwjgl.opengl.GL11; + +import java.util.function.LongConsumer; + +import static org.lwjgl.nanovg.NanoVG.*; +import static org.lwjgl.nanovg.NanoVGGL2.NVG_ANTIALIAS; +import static org.lwjgl.nanovg.NanoVGGL2.nvgCreate; + +/** + * Handles NanoVG rendering and wraps it in a more convenient interface. + */ +public final class RenderManager { + private static long vg = -1; + + //nanovg + + private RenderManager() { + + } + + /** + * Sets up rendering, calls the consumer with the NanoVG context, and then cleans up. + * + * @param consumer The consumer to call. + * @see RenderManager#setupAndDraw(boolean, LongConsumer) + */ + public static void setupAndDraw(LongConsumer consumer) { + setupAndDraw(false, consumer); + } + + /** + * Sets up rendering, calls the consumer with the NanoVG context, and then cleans up. + * + * @param mcScaling Whether to render with Minecraft's scaling. + * @param consumer The consumer to call. + */ + public static void setupAndDraw(boolean mcScaling, LongConsumer consumer) { + if (vg == -1) { + vg = nvgCreate(NVG_ANTIALIAS); + if (vg == -1) { + throw new RuntimeException("Failed to create nvg context"); + } + FontManager.INSTANCE.initialize(vg); + } + + Framebuffer fb = UMinecraft.getMinecraft().getFramebuffer(); + if (!fb.isStencilEnabled()) { + fb.enableStencil(); + } + GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS); + GL11.glDisable(GL11.GL_ALPHA_TEST); + + if (mcScaling) { + nvgBeginFrame(vg, (float) UResolution.getScaledWidth(), (float) UResolution.getScaledHeight(), (float) UResolution.getScaleFactor()); + } else { + // If we get blurry problems with high DPI monitors, 1 might need to be replaced with Display.getPixelScaleFactor() + nvgBeginFrame(vg, UResolution.getWindowWidth(), UResolution.getWindowHeight(), 1); + } + + consumer.accept(vg); + + nvgEndFrame(vg); + + GL11.glPopAttrib(); + } + + /** + * Draws a rectangle with the given parameters. + * + * @param vg The NanoVG context. + * @param x The x position. + * @param y The y position. + * @param width The width. + * @param height The height. + * @param color The color. + */ + public static void drawRectangle(long vg, float x, float y, float width, float height, int color) { // TODO make everything use this one day + if (Colors.ROUNDED_CORNERS) { + drawRoundedRect(vg, x, y, width, height, color, Colors.CORNER_RADIUS); + } else { + drawRect(vg, x, y, width, height, color); + } + } + + /** + * Draws a rectangle with the given parameters. + * + * @param vg The NanoVG context. + * @param x The x position. + * @param y The y position. + * @param width The width. + * @param height The height. + * @param color The color. + */ + public static void drawRect(long vg, float x, float y, float width, float height, int color) { + nvgBeginPath(vg); + nvgRect(vg, x, y, width, height); + NVGColor nvgColor = color(vg, color); + nvgFill(vg); + nvgColor.free(); + } + + /** + * Draws a rounded rectangle with the given parameters. + * + * @param vg The NanoVG context. + * @param x The x position. + * @param y The y position. + * @param width The width. + * @param height The height. + * @param color The color. + * @param radius The radius. + */ + public static void drawRoundedRect(long vg, float x, float y, float width, float height, int color, float radius) { + nvgBeginPath(vg); + nvgRoundedRect(vg, x, y, width, height, radius); + color(vg, color); + NVGColor nvgColor = color(vg, color); + nvgFill(vg); + nvgColor.free(); + } + + /** + * Draw a rounded rectangle where every corner has a different radius + * + * @param vg The NanoVG context + * @param x The x position. + * @param y The y position. + * @param width The width. + * @param height The height. + * @param color The color. + * @param radiusTL Top left corner radius. + * @param radiusTR Top right corner radius. + * @param radiusBR Bottom right corner radius. + * @param radiusBL Bottom left corner radius + */ + public static void drawRoundedRectVaried(long vg, float x, float y, float width, float height, int color, float radiusTL, float radiusTR, float radiusBR, float radiusBL) { + nvgBeginPath(vg); + nvgRoundedRectVarying(vg, x, y, width, height, radiusTL, radiusTR, radiusBR, radiusBL); + color(vg, color); + NVGColor nvgColor = color(vg, color); + nvgFill(vg); + nvgColor.free(); + } + + /** + * Draws a hollow rounded rectangle with the given parameters. + * + * @param vg The NanoVG context. + * @param x The x position. + * @param y The y position. + * @param width The width. + * @param height The height. + * @param color The color. + * @param radius The radius. + * @param thickness The thickness. + */ + public static void drawHollowRoundRect(long vg, float x, float y, float width, float height, int color, float radius, float thickness) { + nvgBeginPath(vg); + nvgRoundedRect(vg, x + thickness, y + thickness, width - thickness, height - thickness, radius); + nvgStrokeWidth(vg, thickness + 0.5f); + nvgPathWinding(vg, NVG_HOLE); + color(vg, color); + NVGColor nvgColor = color(vg, color); + nvgStrokeColor(vg, nvgColor); + nvgStroke(vg); + nvgColor.free(); + } + + /** + * Draws a gradient rectangle with the given parameters. + * + * @param vg The NanoVG context. + * @param x The x position. + * @param y The y position. + * @param width The width. + * @param height The height. + * @param color The first color of the gradient. + * @param color2 The second color of the gradient. + */ + public static void drawGradientRect(long vg, float x, float y, float width, float height, int color, int color2) { + NVGPaint bg = NVGPaint.create(); + nvgBeginPath(vg); + nvgRect(vg, x, y, width, height); + NVGColor nvgColor = color(vg, color); + NVGColor nvgColor2 = color(vg, color2); + nvgFillPaint(vg, nvgLinearGradient(vg, x, y, x, y + width, nvgColor, nvgColor2, bg)); + nvgFillPaint(vg, bg); + nvgFill(vg); + nvgColor.free(); + nvgColor2.free(); + } + + /** + * Draws a rounded gradient rectangle with the given parameters. + * + * @param vg The NanoVG context. + * @param x The x position. + * @param y The y position. + * @param width The width. + * @param height The height. + * @param color The first color of the gradient. + * @param color2 The second color of the gradient. + * @param radius The corner radius. + */ + public static void drawGradientRoundedRect(long vg, float x, float y, float width, float height, int color, int color2, float radius) { + NVGPaint bg = NVGPaint.create(); + nvgBeginPath(vg); + nvgRoundedRect(vg, x, y, width, height, radius); + NVGColor nvgColor = color(vg, color); + NVGColor nvgColor2 = color(vg, color2); + nvgFillPaint(vg, nvgLinearGradient(vg, x, y, x + width, y, nvgColor, nvgColor2, bg)); + nvgFill(vg); + nvgColor.free(); + nvgColor2.free(); + } + + /** + * Draw a HSB box + * + * @param vg The NanoVG context. + * @param x The x coordinate. + * @param y The y coordinate + * @param width The width. + * @param height The height. + * @param colorTarget Hue color + */ + public static void drawHSBBox(long vg, float x, float y, float width, float height, int colorTarget) { + drawRoundedRect(vg, x, y, width, height, colorTarget, 8f); + + NVGPaint bg = NVGPaint.create(); + nvgBeginPath(vg); + nvgRoundedRect(vg, x, y, width, height, 8f); + NVGColor nvgColor = color(vg, -1); + NVGColor nvgColor2 = color(vg, Colors.TRANSPARENT); + nvgFillPaint(vg, nvgLinearGradient(vg, x, y, x + width, y, nvgColor, nvgColor2, bg)); + nvgFill(vg); + nvgColor.free(); + nvgColor2.free(); + + NVGPaint bg2 = NVGPaint.create(); + nvgBeginPath(vg); + nvgRoundedRect(vg, x, y, width, height, 8f); + NVGColor nvgColor3 = color(vg, Colors.TRANSPARENT); + NVGColor nvgColor4 = color(vg, Colors.BLACK); + nvgFillPaint(vg, nvgLinearGradient(vg, x, y, x, y + height, nvgColor3, nvgColor4, bg2)); + nvgFill(vg); + nvgColor3.free(); + nvgColor4.free(); + } + + /** + * Draws a circle with the given parameters. + * + * @param vg The NanoVG context. + * @param x The x position. + * @param y The y position. + * @param radius The radius. + * @param color The color. + */ + public static void drawCircle(long vg, float x, float y, float radius, int color) { + nvgBeginPath(vg); + nvgCircle(vg, x, y, radius); + NVGColor nvgColor = color(vg, color); + nvgFill(vg); + nvgColor.free(); + } + + /** + * Draws a String with the given parameters. + * + * @param vg The NanoVG context. + * @param text The text. + * @param x The x position. + * @param y The y position. + * @param color The color. + * @param size The size. + * @param font The font. + * @see cc.polyfrost.oneconfig.renderer.font.Font + */ + public static void drawText(long vg, String text, float x, float y, int color, float size, Fonts font) { + drawText(vg, text, x, y, color, size, font.font); + } + + /** + * Draws a String with the given parameters. + * + * @param vg The NanoVG context. + * @param text The text. + * @param x The x position. + * @param y The y position. + * @param color The color. + * @param size The size. + * @param font The font. + * @see cc.polyfrost.oneconfig.renderer.font.Font + */ + public static void drawText(long vg, String text, float x, float y, int color, float size, Font font) { + nvgBeginPath(vg); + nvgFontSize(vg, size); + nvgFontFace(vg, font.getName()); + nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); + NVGColor nvgColor = color(vg, color); + nvgText(vg, x, y, text); + nvgFill(vg); + nvgColor.free(); + } + + /** + * Draws a String wrapped at the given width, with the given parameters. + * + * @param vg The NanoVG context. + * @param text The text. + * @param x The x position. + * @param y The y position. + * @param width The width. + * @param color The color. + * @param size The size. + * @param font The font. + */ + public static void drawWrappedString(long vg, String text, float x, float y, float width, int color, float size, Fonts font) { + drawWrappedString(vg, text, x, y, width, color, size, font.font); + } + + /** + * Draws a String wrapped at the given width, with the given parameters. + * + * @param vg The NanoVG context. + * @param text The text. + * @param x The x position. + * @param y The y position. + * @param width The width. + * @param color The color. + * @param size The size. + * @param font The font. + */ + public static void drawWrappedString(long vg, String text, float x, float y, float width, int color, float size, Font font) { + nvgBeginPath(vg); + nvgFontSize(vg, size); + nvgFontFace(vg, font.getName()); + nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); + NVGColor nvgColor = color(vg, color); + nvgTextBox(vg, x, y, width, text); + nvgFill(vg); + nvgColor.free(); + } + + /** + * Draw a formatted URL (a string in blue with an underline) that when clicked, opens the given text. + * + *

This does NOT scale to Minecraft's GUI scale!

+ * + * @see RenderManager#drawText(long, String, float, float, int, float, Font) + * @see InputUtils#isAreaClicked(int, int, int, int) + */ + public static void drawURL(long vg, String url, float x, float y, float size, Fonts font) { + drawURL(vg, url, x, y, size, font.font); + } + + /** + * Draw a formatted URL (a string in blue with an underline) that when clicked, opens the given text. + * + *

This does NOT scale to Minecraft's GUI scale!

+ * + * @see RenderManager#drawText(long, String, float, float, int, float, Font) + * @see InputUtils#isAreaClicked(int, int, int, int) + */ + public static void drawURL(long vg, String url, float x, float y, float size, Font font) { + drawText(vg, url, x, y, Colors.PRIMARY_500, size, font); + float length = getTextWidth(vg, url, size, font); + drawRectangle(vg, x, y + size / 2, length, 1, Colors.PRIMARY_500); + if (InputUtils.isAreaClicked((int) (x - 2), (int) (y - 1), (int) (length + 4), (int) (size / 2 + 3))) { + NetworkUtils.browseLink(url); + } + } + + /** + * Draws an image with the provided file path. + * + * @param vg The NanoVG context. + * @param filePath The file path. + * @param x The x position. + * @param y The y position. + * @param width The width. + * @param height The height. + * @see RenderManager#drawImage(long, String, float, float, float, float, int) + */ + public static void drawImage(long vg, String filePath, float x, float y, float width, float height) { + if (ImageLoader.INSTANCE.loadImage(vg, filePath)) { + NVGPaint imagePaint = NVGPaint.calloc(); + int image = ImageLoader.INSTANCE.getImage(filePath); + nvgBeginPath(vg); + nvgImagePattern(vg, x, y, width, height, 0, image, 1, imagePaint); + nvgRect(vg, x, y, width, height); + nvgFillPaint(vg, imagePaint); + nvgFill(vg); + imagePaint.free(); + } + } + + /** + * Draws an image with the provided file path. + * + * @param vg The NanoVG context. + * @param filePath The file path. + * @param x The x position. + * @param y The y position. + * @param width The width. + * @param height The height. + * @param color The color. + */ + public static void drawImage(long vg, String filePath, float x, float y, float width, float height, int color) { + if (ImageLoader.INSTANCE.loadImage(vg, filePath)) { + NVGPaint imagePaint = NVGPaint.calloc(); + int image = ImageLoader.INSTANCE.getImage(filePath); + nvgBeginPath(vg); + nvgImagePattern(vg, x, y, width, height, 0, image, 1, imagePaint); + nvgRGBA((byte) (color >> 16 & 0xFF), (byte) (color >> 8 & 0xFF), (byte) (color & 0xFF), (byte) (color >> 24 & 0xFF), imagePaint.innerColor()); + nvgRect(vg, x, y, width, height); + nvgFillPaint(vg, imagePaint); + nvgFill(vg); + imagePaint.free(); + } + } + + /** + * Draws an image with the provided file path and parameters. + * + * @see RenderManager#drawImage(long, String, float, float, float, float) + */ + public static void drawImage(long vg, Images filePath, float x, float y, float width, float height) { + drawImage(vg, filePath.filePath, x, y, width, height); + } + + /** + * Draws an image with the provided file path and parameters. + * + * @see RenderManager#drawImage(long, String, float, float, float, float, int) + */ + public static void drawImage(long vg, Images filePath, float x, float y, float width, float height, int color) { + drawImage(vg, filePath.filePath, x, y, width, height, color); + } + + /** + * Draws a rounded image with the provided file path and parameters. + * + * @param vg The NanoVG context. + * @param filePath The file path. + * @param x The x position. + * @param y The y position. + * @param width The width. + * @param height The height. + * @param radius The radius. + */ + public static void drawRoundImage(long vg, String filePath, float x, float y, float width, float height, float radius) { + if (ImageLoader.INSTANCE.loadImage(vg, filePath)) { + NVGPaint imagePaint = NVGPaint.calloc(); + int image = ImageLoader.INSTANCE.getImage(filePath); + nvgBeginPath(vg); + nvgImagePattern(vg, x, y, width, height, 0, image, 1, imagePaint); + nvgRoundedRect(vg, x, y, width, height, radius); + nvgFillPaint(vg, imagePaint); + nvgFill(vg); + imagePaint.free(); + } + } + + /** + * Draws a rounded image with the provided file path and parameters. + * + * @see RenderManager#drawRoundImage(long, String, float, float, float, float, float) + */ + public static void drawRoundImage(long vg, Images filePath, float x, float y, float width, float height, float radius) { + drawRoundImage(vg, filePath.filePath, x, y, width, height, radius); + } + + public static float getTextWidth(long vg, String text, float fontSize, Fonts font) { + return getTextWidth(vg, text, fontSize, font.font); + } + + /** + * Get the width of the provided String. + * + * @param vg The NanoVG context. + * @param text The text. + * @param fontSize The font size. + * @param font The font. + * @return The width of the text. + */ + public static float getTextWidth(long vg, String text, float fontSize, Font font) { + float[] bounds = new float[4]; + nvgFontSize(vg, fontSize); + nvgFontFace(vg, font.getName()); + return nvgTextBounds(vg, 0, 0, text, bounds); + } + + /** + * Draws a line with the provided parameters. + * + * @param vg The NanoVG context. + * @param x The x position. + * @param y The y position. + * @param endX The end x position. + * @param endY The end y position. + * @param width The width. + * @param color The color. + */ + public static void drawLine(long vg, float x, float y, float endX, float endY, float width, int color) { + nvgBeginPath(vg); + nvgMoveTo(vg, x, y); + nvgLineTo(vg, endX, endY); + NVGColor nvgColor = color(vg, color); + nvgStrokeColor(vg, nvgColor); + nvgStrokeWidth(vg, width); + nvgStroke(vg); + nvgColor.free(); + } + + /** + * Draw a drop shadow. + * + * Adapted from legui under MIT license + * + * @param vg The NanoVG context. + * @param x The x coordinate. + * @param y The y coordinate. + * @param w The width. + * @param h The height. + * @param blur The blur (feather). + * @param spread The spread. + * @param cornerRadius The radius of the corner + */ + public static void drawDropShadow(long vg, float x, float y, float w, float h, float blur, float spread, float cornerRadius) { + try (NVGPaint shadowPaint = NVGPaint.calloc(); // allocating memory to pass color to nanovg wrapper + NVGColor firstColor = NVGColor.calloc(); // allocating memory to pass color to nanovg wrapper + NVGColor secondColor = NVGColor.calloc() // allocating memory to pass color to nanovg wrapper + ) { + fillNVGColorWithRGBA(0, 0, 0, 0.5f, firstColor); // filling allocated memory + fillNVGColorWithRGBA(0, 0, 0, 0, secondColor); // filling allocated memory + + // creating gradient and put it to shadowPaint + nvgBoxGradient(vg, x - spread, y - spread, w + 2 * spread, h + 2 * spread, cornerRadius + spread, blur, firstColor, secondColor, shadowPaint); + nvgBeginPath(vg); + nvgRoundedRect(vg, x - spread - blur, y - spread - blur, w + 2 * spread + 2 * blur, h + 2 * spread + 2 * blur, cornerRadius + spread); + nvgRoundedRect(vg, x, y, w, h, cornerRadius); + nvgPathWinding(vg, NVG_HOLE); + nvgFillPaint(vg, shadowPaint); + nvgFill(vg); + } + } + + /** + * Fills the provided {@link NVGColor} with the provided RGBA values. + * + * @param r The red value. + * @param g The green value. + * @param b The blue value. + * @param a The alpha value. + * @param color The {@link NVGColor} to fill. + */ + public static void fillNVGColorWithRGBA(float r, float g, float b, float a, NVGColor color) { + color.r(r); + color.g(g); + color.b(b); + color.a(a); + } + + /** + * Create a {@link NVGColor} from the provided RGBA values. + * + * @param vg The NanoVG context. + * @param color The color. + * @return The {@link NVGColor} created. + */ + public static NVGColor color(long vg, int color) { + NVGColor nvgColor = NVGColor.calloc(); + nvgRGBA((byte) (color >> 16 & 0xFF), (byte) (color >> 8 & 0xFF), (byte) (color & 0xFF), (byte) (color >> 24 & 0xFF), nvgColor); + nvgFillColor(vg, nvgColor); + return nvgColor; + } + + /** + * Scales all rendering by the provided scale. + * + * @param vg The NanoVG context. + * @param x The x scale. + * @param y The y scale. + */ + public static void scale(long vg, float x, float y) { + nvgScale(vg, x, y); + } + + /** + * Sets the global alpha value to render with. + * + * @param vg The NanoVG context. + * @param alpha The alpha value. + */ + public static void setAlpha(long vg, float alpha) { + nvgGlobalAlpha(vg, alpha); + } + + /** + * Draws a SVG with the provided file path and parameters. + * + * @param vg The NanoVG context. + * @param filePath The file path. + * @param x The x position. + * @param y The y position. + * @param width The width. + * @param height The height. + */ + public static void drawSvg(long vg, String filePath, float x, float y, float width, float height) { + float w = width; + float h = height; + if (OneConfigGui.INSTANCE != null) { + w *= OneConfigGui.INSTANCE.getScaleFactor(); + h *= OneConfigGui.INSTANCE.getScaleFactor(); + } + if (ImageLoader.INSTANCE.loadSVG(vg, filePath, w, h)) { + NVGPaint imagePaint = NVGPaint.calloc(); + int image = ImageLoader.INSTANCE.getSVG(filePath, w, h); + nvgBeginPath(vg); + nvgImagePattern(vg, x, y, width, height, 0, image, 1, imagePaint); + nvgRect(vg, x, y, width, height); + nvgFillPaint(vg, imagePaint); + nvgFill(vg); + imagePaint.free(); + } + } + + /** + * Draws a SVG with the provided file path and parameters. + * + * @param vg The NanoVG context. + * @param filePath The file path. + * @param x The x position. + * @param y The y position. + * @param width The width. + * @param height The height. + * @param color The color. + */ + public static void drawSvg(long vg, String filePath, float x, float y, float width, float height, int color) { + float w = width; + float h = height; + if (OneConfigGui.INSTANCE != null) { + w *= OneConfigGui.INSTANCE.getScaleFactor(); + h *= OneConfigGui.INSTANCE.getScaleFactor(); + } + if (ImageLoader.INSTANCE.loadSVG(vg, filePath, w, h)) { + NVGPaint imagePaint = NVGPaint.calloc(); + int image = ImageLoader.INSTANCE.getSVG(filePath, w, h); + nvgBeginPath(vg); + nvgImagePattern(vg, x, y, width, height, 0, image, 1, imagePaint); + nvgRGBA((byte) (color >> 16 & 0xFF), (byte) (color >> 8 & 0xFF), (byte) (color & 0xFF), (byte) (color >> 24 & 0xFF), imagePaint.innerColor()); + nvgRect(vg, x, y, width, height); + nvgFillPaint(vg, imagePaint); + nvgFill(vg); + imagePaint.free(); + } + } + + /** + * Draws an SVG with the provided file path and parameters. + * + * @see RenderManager#drawSvg(long, String, float, float, float, float) + */ + public static void drawSvg(long vg, SVGs svg, float x, float y, float width, float height) { + drawSvg(vg, svg.filePath, x, y, width, height); + } + + /** + * Draws an SVG with the provided file path and parameters. + * + * @see RenderManager#drawSvg(long, String, float, float, float, float, int) + */ + public static void drawSvg(long vg, SVGs svg, float x, float y, float width, float height, int color) { + drawSvg(vg, svg.filePath, x, y, width, height, color); + } + + /** + * Draw a circle with an info icon inside of it + * + * @param vg The NanoVG context. + * @param type The icon type. + * @param x The x position. + * @param y The y position. + * @param size The diameter. + */ + public static void drawInfo(long vg, InfoType type, float x, float y, float size) { + SVGs icon = null; + int colorOuter = 0; + int colorInner = 0; + switch (type) { + case INFO: + icon = SVGs.INFO_CIRCLE; + colorOuter = Colors.GRAY_400; + colorInner = Colors.GRAY_300; + break; + case SUCCESS: + icon = SVGs.CHECK_CIRCLE; + colorOuter = Colors.SUCCESS_700; + colorInner = Colors.SUCCESS_600; + break; + case WARNING: + icon = SVGs.WARNING; + colorOuter = Colors.WARNING_600; + colorInner = Colors.WARNING_500; + break; + case ERROR: + icon = SVGs.ERROR; + colorOuter = Colors.ERROR_700; + colorInner = Colors.ERROR_600; + break; + } + float centerX = x + size / 2f; + float centerY = y + size / 2f; + drawCircle(vg, centerX, centerY, size / 2, colorOuter); + drawCircle(vg, centerX, centerY, size / 2 - size / 12, colorInner); + float iconSize = size / 1.75f; + drawSvg(vg, icon, centerX - iconSize / 2f, centerY - iconSize / 2f, iconSize, iconSize); + } + + // gl + + public static void drawScaledString(String text, float x, float y, int color, boolean shadow, float scale) { + UGraphics.GL.pushMatrix(); + UGraphics.GL.scale(scale, scale, 1); + UMinecraft.getFontRenderer().drawString(text, x * (1 / scale), y * (1 / scale), color, shadow); + UGraphics.GL.popMatrix(); + } + + public static void drawGlRect(int x, int y, int width, int height, int color) { + Gui.drawRect(x, y, x + width, y + height, color); + } +} diff --git a/src/main/java/cc/polyfrost/oneconfig/renderer/font/Font.java b/src/main/java/cc/polyfrost/oneconfig/renderer/font/Font.java new file mode 100644 index 0000000..b35708b --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/renderer/font/Font.java @@ -0,0 +1,41 @@ +package cc.polyfrost.oneconfig.renderer.font; + +import java.nio.ByteBuffer; + +public class Font { + private final String fileName; + private final String name; + private boolean loaded = false; + private ByteBuffer buffer = null; + + public Font(String name, String fileName) { + this.name = name; + this.fileName = fileName; + } + + public String getName() { + return name; + } + + public String getFileName() { + return fileName; + } + + public boolean isLoaded() { + return loaded; + } + + void setLoaded(boolean loaded) { + this.loaded = loaded; + } + + public ByteBuffer getBuffer() { + return buffer; + } + + void setBuffer(ByteBuffer buffer) { + this.buffer = buffer; + } + +} + diff --git a/src/main/java/cc/polyfrost/oneconfig/renderer/font/FontManager.java b/src/main/java/cc/polyfrost/oneconfig/renderer/font/FontManager.java new file mode 100644 index 0000000..388911d --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/renderer/font/FontManager.java @@ -0,0 +1,47 @@ +package cc.polyfrost.oneconfig.renderer.font; + +import cc.polyfrost.oneconfig.utils.IOUtils; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import static org.lwjgl.nanovg.NanoVG.nvgCreateFontMem; + +public class FontManager { + public static FontManager INSTANCE = new FontManager(); + + /** + * Load all fonts in the Fonts class + * + * @param vg NanoVG context + */ + + public void initialize(long vg) { + for (Fonts fonts : Fonts.values()) { + loadFont(vg, fonts.font); + } + } + + /** + * Load a font into NanoVG + * + * @param vg NanoVG context + * @param font The font to be loaded + */ + public void loadFont(long vg, Font font) { + if (font.isLoaded()) return; + int loaded = -1; + try { + ByteBuffer buffer = IOUtils.resourceToByteBuffer(font.getFileName()); + loaded = nvgCreateFontMem(vg, font.getName(), buffer, 0); + font.setBuffer(buffer); + } catch (IOException e) { + e.printStackTrace(); + } + if (loaded == -1) { + throw new RuntimeException("Failed to initialize font " + font.getName()); + } else { + font.setLoaded(true); + } + } +} diff --git a/src/main/java/cc/polyfrost/oneconfig/renderer/font/Fonts.java b/src/main/java/cc/polyfrost/oneconfig/renderer/font/Fonts.java new file mode 100644 index 0000000..9b6193f --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/renderer/font/Fonts.java @@ -0,0 +1,15 @@ +package cc.polyfrost.oneconfig.renderer.font; + +public enum Fonts { + BOLD(new Font("inter-bold", "/assets/oneconfig/font/Bold.otf")), + SEMIBOLD(new Font("inter-semibold", "/assets/oneconfig/font/SemiBold.otf")), + MEDIUM(new Font("inter-medium", "/assets/oneconfig/font/Medium.otf")), + REGULAR(new Font("inter-regular", "/assets/oneconfig/font/Regular.otf")), + MINECRAFT_REGULAR(new Font("mc-regular", "/assets/oneconfig/font/Minecraft-Regular.otf")), + MINECRAFT_BOLD(new Font("mc-bold", "/assets/oneconfig/font/Minecraft-Bold.otf")); + public final Font font; + + Fonts(Font font) { + this.font = font; + } +} diff --git a/src/main/java/cc/polyfrost/oneconfig/renderer/image/ImageLoader.java b/src/main/java/cc/polyfrost/oneconfig/renderer/image/ImageLoader.java new file mode 100644 index 0000000..e8861eb --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/renderer/image/ImageLoader.java @@ -0,0 +1,190 @@ +package cc.polyfrost.oneconfig.renderer.image; + +import cc.polyfrost.oneconfig.utils.IOUtils; +import org.lwjgl.nanovg.NSVGImage; +import org.lwjgl.nanovg.NanoSVG; +import org.lwjgl.nanovg.NanoVG; +import org.lwjgl.stb.STBImage; +import org.lwjgl.system.MemoryUtil; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.ByteBuffer; +import java.util.HashMap; + +/** + * Loads images and SVGs from resources into NanoVG. + * + * @see cc.polyfrost.oneconfig.renderer.RenderManager + * @see Images + * @see SVGs + */ +public final class ImageLoader { + private ImageLoader() { + + } + + private final HashMap imageHashMap = new HashMap<>(); + private final HashMap svgHashMap = new HashMap<>(); + public static ImageLoader INSTANCE = new ImageLoader(); + + /** + * Loads an image from resources. + * + * @param vg The NanoVG context. + * @param fileName The name of the file to load. + * @return Whether the image was loaded successfully. + */ + public boolean loadImage(long vg, String fileName) { + if (!imageHashMap.containsKey(fileName)) { + int[] width = {0}; + int[] height = {0}; + int[] channels = {0}; + + ByteBuffer image = IOUtils.resourceToByteBufferNullable(fileName); + if (image == null) { + return false; + } + + ByteBuffer buffer = STBImage.stbi_load_from_memory(image, width, height, channels, 4); + if (buffer == null) { + return false; + } + + imageHashMap.put(fileName, NanoVG.nvgCreateImageRGBA(vg, width[0], height[0], NanoVG.NVG_IMAGE_REPEATX | NanoVG.NVG_IMAGE_REPEATY | NanoVG.NVG_IMAGE_GENERATE_MIPMAPS, buffer)); + return true; + } + return true; + } + + /** + * Loads an SVG from resources. + * + * @param vg The NanoVG context. + * @param fileName The name of the file to load. + * @param width The width of the SVG. + * @param height The height of the SVG. + * @return Whether the SVG was loaded successfully. + */ + public boolean loadSVG(long vg, String fileName, float width, float height) { + String name = fileName + "-" + width + "-" + height; + if (!svgHashMap.containsKey(name)) { + try { + InputStream inputStream = this.getClass().getResourceAsStream(fileName); + if (inputStream == null) return false; + StringBuilder resultStringBuilder = new StringBuilder(); + try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) { + String line; + while ((line = br.readLine()) != null) { + resultStringBuilder.append(line); + } + } + CharSequence s = resultStringBuilder.toString(); + NSVGImage svg = NanoSVG.nsvgParse(s, "px", 96f); + if (svg == null) return false; + long rasterizer = NanoSVG.nsvgCreateRasterizer(); + + int w = (int) svg.width(); + int h = (int) svg.height(); + float scale = Math.max(width / w, height / h); + w = (int) (w * scale); + h = (int) (h * scale); + + ByteBuffer image = MemoryUtil.memAlloc(w * h * 4); + NanoSVG.nsvgRasterize(rasterizer, svg, 0, 0, scale, image, w, h, w * 4); + + NanoSVG.nsvgDeleteRasterizer(rasterizer); + NanoSVG.nsvgDelete(svg); + + svgHashMap.put(name, NanoVG.nvgCreateImageRGBA(vg, w, h, NanoVG.NVG_IMAGE_REPEATX | NanoVG.NVG_IMAGE_REPEATY | NanoVG.NVG_IMAGE_GENERATE_MIPMAPS, image)); + return true; + } catch (Exception e) { + System.err.println("Failed to parse SVG file"); + e.printStackTrace(); + return false; + } + } + return true; + } + + /** + * Get a loaded image from the cache. + *

Requires the image to have been loaded first.

+ * + * @param fileName The name of the file to load. + * @return The image + * @see ImageLoader#loadImage(long, String) + */ + public int getImage(String fileName) { + return imageHashMap.get(fileName); + } + + /** + * Remove an image from the cache, allowing the image to be garbage collected. + * Should be used when the GUI rendering the image is closed. + * + * @param vg The NanoVG context. + * @param fileName The name of the file to remove. + * @see ImageLoader#loadImage(long, String) + */ + public void removeImage(long vg, String fileName) { + NanoVG.nvgDeleteImage(vg, imageHashMap.get(fileName)); + imageHashMap.remove(fileName); + } + + /** + * Clears all images from the cache, allowing the images cleared to be garbage collected. + * Should be used when the GUI rendering loaded images are closed. + * + * @param vg The NanoVG context. + */ + public void clearImages(long vg) { + HashMap temp = new HashMap<>(imageHashMap); + for (String image : temp.keySet()) { + NanoVG.nvgDeleteImage(vg, imageHashMap.get(image)); + imageHashMap.remove(image); + } + } + + /** + * Get a loaded SVG from the cache. + *

Requires the SVG to have been loaded first.

+ * + * @param fileName The name of the file to load. + * @return The SVG + * @see ImageLoader#loadSVG(long, String, float, float) + */ + public int getSVG(String fileName, float width, float height) { + String name = fileName + "-" + width + "-" + height; + return svgHashMap.get(name); + } + + /** + * Remove a SVG from the cache, allowing the SVG to be garbage collected. + * Should be used when the GUI rendering the SVG is closed. + * + * @param vg The NanoVG context. + * @param fileName The name of the file to remove. + * @see ImageLoader#loadSVG(long, String, float, float) + */ + public void removeSVG(long vg, String fileName, float width, float height) { + String name = fileName + "-" + width + "-" + height; + NanoVG.nvgDeleteImage(vg, imageHashMap.get(name)); + svgHashMap.remove(name); + } + + /** + * Clears all SVGs from the cache, allowing the SVGs cleared to be garbage collected. + * Should be used when the GUI rendering loaded SVGs are closed. + * + * @param vg The NanoVG context. + */ + public void clearSVGs(long vg) { + HashMap temp = new HashMap<>(svgHashMap); + for (String image : temp.keySet()) { + NanoVG.nvgDeleteImage(vg, svgHashMap.get(image)); + svgHashMap.remove(image); + } + } +} diff --git a/src/main/java/cc/polyfrost/oneconfig/renderer/image/Images.java b/src/main/java/cc/polyfrost/oneconfig/renderer/image/Images.java new file mode 100644 index 0000000..ad1941e --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/renderer/image/Images.java @@ -0,0 +1,19 @@ +package cc.polyfrost.oneconfig.renderer.image; + +/** + * An enum of images used in OneConfig. + * + * @see cc.polyfrost.oneconfig.renderer.RenderManager#drawImage(long, String, float, float, float, float, int) + * @see ImageLoader + */ +public enum Images { + HUE_GRADIENT("/assets/oneconfig/options/HueGradient.png"), + COLOR_WHEEL("/assets/oneconfig/options/ColorWheel.png"), + ALPHA_GRID("/assets/oneconfig/options/AlphaGrid.png"); + + public final String filePath; + + Images(String filePath) { + this.filePath = filePath; + } +} \ No newline at end of file diff --git a/src/main/java/cc/polyfrost/oneconfig/renderer/image/SVGs.java b/src/main/java/cc/polyfrost/oneconfig/renderer/image/SVGs.java new file mode 100644 index 0000000..5ba3fcd --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/renderer/image/SVGs.java @@ -0,0 +1,52 @@ +package cc.polyfrost.oneconfig.renderer.image; + +/** + * An enum of SVGs used in OneConfig. + * + * @see cc.polyfrost.oneconfig.renderer.RenderManager#drawSvg(long, String, float, float, float, float, int) + * @see ImageLoader + */ +public enum SVGs { + ONECONFIG("/assets/oneconfig/icons/OneConfig.svg"), + ONECONFIG_OFF("/assets/oneconfig/icons/OneConfigOff.svg"), + COPYRIGHT_FILL("/assets/oneconfig/icons/CopyrightFill.svg"), + APERTURE_FILL("/assets/oneconfig/icons/ApertureFill.svg"), + ARROWS_CLOCKWISE_BOLD("/assets/oneconfig/icons/ArrowsClockwiseBold.svg"), + FADERS_HORIZONTAL_BOLD("/assets/oneconfig/icons/FadersHorizontalBold.svg"), + GAUGE_FILL("/assets/oneconfig/icons/GaugeFill.svg"), + GEAR_SIX_FILL("/assets/oneconfig/icons/GearSixFill.svg"), + MAGNIFYING_GLASS_BOLD("/assets/oneconfig/icons/MagnifyingGlassBold.svg"), + NOTE_PENCIL_BOLD("/assets/oneconfig/icons/NotePencilBold.svg"), + PAINT_BRUSH_BROAD_FILL("/assets/oneconfig/icons/PaintBrushBroadFill.svg"), + USER_SWITCH_FILL("/assets/oneconfig/icons/UserSwitchFill.svg"), + X_CIRCLE_BOLD("/assets/oneconfig/icons/XCircleBold.svg"), + CARET_LEFT("/assets/oneconfig/icons/CaretLeftBold.svg"), + CARET_RIGHT("/assets/oneconfig/icons/CaretRightBold.svg"), + + // OLD ICONS + BOX("/assets/oneconfig/old-icons/Box.svg"), + CHECKBOX_TICK("/assets/oneconfig/old-icons/CheckboxTick.svg"), + CHECK_CIRCLE("/assets/oneconfig/old-icons/CheckCircle.svg"), + CHEVRON_DOWN("/assets/oneconfig/old-icons/ChevronDown.svg"), + CHEVRON_UP("/assets/oneconfig/old-icons/ChevronUp.svg"), + COPY("/assets/oneconfig/old-icons/Copy.svg"), + DROPDOWN_LIST("/assets/oneconfig/old-icons/DropdownList.svg"), + ERROR("/assets/oneconfig/old-icons/Error.svg"), + EYE("/assets/oneconfig/old-icons/Eye.svg"), + EYE_OFF("/assets/oneconfig/old-icons/EyeOff.svg"), + HEART_FILL("/assets/oneconfig/old-icons/HeartFill.svg"), + HEART_OUTLINE("/assets/oneconfig/old-icons/HeartOutline.svg"), + HELP_CIRCLE("/assets/oneconfig/old-icons/HelpCircle.svg"), + HISTORY("/assets/oneconfig/old-icons/History.svg"), + INFO_CIRCLE("/assets/oneconfig/old-icons/InfoCircle.svg"), + KEYSTROKE("/assets/oneconfig/old-icons/Keystroke.svg"), + PASTE("/assets/oneconfig/old-icons/Paste.svg"), + POP_OUT("/assets/oneconfig/old-icons/PopOut.svg"), + WARNING("/assets/oneconfig/old-icons/Warning.svg"); + + public final String filePath; + + SVGs(String filePath) { + this.filePath = filePath; + } +} diff --git a/src/main/java/cc/polyfrost/oneconfig/renderer/scissor/Scissor.java b/src/main/java/cc/polyfrost/oneconfig/renderer/scissor/Scissor.java new file mode 100644 index 0000000..179a260 --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/renderer/scissor/Scissor.java @@ -0,0 +1,27 @@ +package cc.polyfrost.oneconfig.renderer.scissor; + +/** + * A class that represents a scissor rectangle. + * + * @see ScissorManager + */ +public class Scissor { + public float x; + public float y; + public float width; + public float height; + + public Scissor(float x, float y, float width, float height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + public Scissor(Scissor scissor) { + this.x = scissor.x; + this.y = scissor.y; + this.width = scissor.width; + this.height = scissor.height; + } +} diff --git a/src/main/java/cc/polyfrost/oneconfig/renderer/scissor/ScissorManager.java b/src/main/java/cc/polyfrost/oneconfig/renderer/scissor/ScissorManager.java new file mode 100644 index 0000000..1f7801b --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/renderer/scissor/ScissorManager.java @@ -0,0 +1,69 @@ +package cc.polyfrost.oneconfig.renderer.scissor; + +import org.lwjgl.nanovg.NanoVG; + +import java.util.ArrayList; + +/** + * Provides an easy way to manage and group scissor rectangles. + */ +public class ScissorManager { + private static final ArrayList scissors = new ArrayList<>(); + + /** + * Adds and applies a scissor rectangle to the list of scissor rectangles. + * + * @param vg The NanoVG context. + * @param x The x coordinate of the scissor rectangle. + * @param y The y coordinate of the scissor rectangle. + * @param width The width of the scissor rectangle. + * @param height The height of the scissor rectangle. + * @return The scissor rectangle. + */ + public static Scissor scissor(long vg, float x, float y, float width, float height) { + Scissor scissor = new Scissor(x, y, width, height); + if (scissors.contains(scissor)) return scissor; + scissors.add(scissor); + applyScissors(vg); + return scissor; + } + + /** + * Resets the scissor rectangle provided. + * + * @param vg The NanoVG context. + * @param scissor The scissor rectangle to reset. + */ + public static void resetScissor(long vg, Scissor scissor) { + if (scissors.contains(scissor)) { + scissors.remove(scissor); + applyScissors(vg); + } + } + + /** + * Clear all scissor rectangles. + * + * @param vg The NanoVG context. + */ + public static void clearScissors(long vg) { + scissors.clear(); + NanoVG.nvgResetScissor(vg); + } + + private static void applyScissors(long vg) { + NanoVG.nvgResetScissor(vg); + if (scissors.size() <= 0) return; + Scissor finalScissor = new Scissor(scissors.get(0)); + for (int i = 1; i < scissors.size(); i++) { + Scissor scissor = scissors.get(i); + float rightX = Math.min(scissor.x + scissor.width, finalScissor.x + finalScissor.width); + float rightY = Math.min(scissor.y + scissor.height, finalScissor.y + finalScissor.height); + finalScissor.x = Math.max(finalScissor.x, scissor.x); + finalScissor.y = Math.max(finalScissor.y, scissor.y); + finalScissor.width = rightX - finalScissor.x; + finalScissor.height = rightY - finalScissor.y; + } + NanoVG.nvgScissor(vg, finalScissor.x, finalScissor.y, finalScissor.width, finalScissor.height); + } +} -- cgit