|
| 1 | +/* |
| 2 | + * Copyright (c) 2014-2026 Wurst-Imperium and contributors. |
| 3 | + * |
| 4 | + * This source code is subject to the terms of the GNU General Public |
| 5 | + * License, version 3. If a copy of the GPL was not distributed with this |
| 6 | + * file, You can obtain one at: https://www.gnu.org/licenses/gpl-3.0.txt |
| 7 | + */ |
| 8 | +package net.wurstclient.options; |
| 9 | + |
| 10 | +import net.minecraft.client.gui.GuiGraphicsExtractor; |
| 11 | +import net.minecraft.client.gui.components.Button; |
| 12 | +import net.minecraft.client.gui.components.EditBox; |
| 13 | +import net.minecraft.client.gui.components.MultiLineEditBox; |
| 14 | +import net.minecraft.client.gui.screens.Screen; |
| 15 | +import net.minecraft.network.chat.Component; |
| 16 | +import net.minecraft.util.CommonColors; |
| 17 | +import net.wurstclient.WurstClient; |
| 18 | +import net.wurstclient.other_features.WurstOptionsOtf; |
| 19 | +import net.wurstclient.settings.TextFieldSetting; |
| 20 | +import net.wurstclient.util.ShadertoyBackgroundManager; |
| 21 | +import net.wurstclient.util.WurstColors; |
| 22 | + |
| 23 | +public final class CustomShadertoyBackgroundScreen extends Screen |
| 24 | +{ |
| 25 | + private final Screen prevScreen; |
| 26 | + private EditBox urlBox; |
| 27 | + private MultiLineEditBox codeBox; |
| 28 | + private String status = ""; |
| 29 | + private boolean loading; |
| 30 | + |
| 31 | + public CustomShadertoyBackgroundScreen(Screen prevScreen) |
| 32 | + { |
| 33 | + super(Component.literal("Import Shadertoy Background")); |
| 34 | + this.prevScreen = prevScreen; |
| 35 | + } |
| 36 | + |
| 37 | + @Override |
| 38 | + protected void init() |
| 39 | + { |
| 40 | + WurstOptionsOtf options = |
| 41 | + WurstClient.INSTANCE.getOtfs().wurstOptionsOtf; |
| 42 | + TextFieldSetting urlSetting = |
| 43 | + options.getTitleScreenShadertoyUrlSetting(); |
| 44 | + |
| 45 | + int boxWidth = Math.min(760, width - 40); |
| 46 | + int left = width / 2 - boxWidth / 2; |
| 47 | + int urlY = 58; |
| 48 | + |
| 49 | + urlBox = new EditBox(font, left, urlY, boxWidth, 20, |
| 50 | + Component.literal("Shadertoy URL")); |
| 51 | + urlBox.setMaxLength(512); |
| 52 | + urlBox.setValue(urlSetting.getValue()); |
| 53 | + urlBox.setHint( |
| 54 | + Component.literal("https://www.shadertoy.com/view/Nfc3RM")); |
| 55 | + urlBox.setResponder(urlSetting::setValue); |
| 56 | + addRenderableWidget(urlBox); |
| 57 | + |
| 58 | + int editorY = urlY + 48; |
| 59 | + int editorHeight = Math.max(120, height - editorY - 96); |
| 60 | + codeBox = MultiLineEditBox.builder().setX(left).setY(editorY) |
| 61 | + .setPlaceholder(Component.literal( |
| 62 | + "Paste raw Shadertoy code here, including mainImage(...).")) |
| 63 | + .build(font, boxWidth, editorHeight, |
| 64 | + Component.literal("Shadertoy code")); |
| 65 | + codeBox.setCharacterLimit(262144); |
| 66 | + codeBox.setValue(ShadertoyBackgroundManager.loadRawShader()); |
| 67 | + addRenderableWidget(codeBox); |
| 68 | + |
| 69 | + int buttonY = editorY + editorHeight + 10; |
| 70 | + int gap = 6; |
| 71 | + int buttonWidth = 112; |
| 72 | + int totalWidth = buttonWidth * 4 + gap * 3; |
| 73 | + int buttonX = width / 2 - totalWidth / 2; |
| 74 | + |
| 75 | + addRenderableWidget( |
| 76 | + Button.builder(Component.literal("Load URL"), b -> loadUrl()) |
| 77 | + .bounds(buttonX, buttonY, buttonWidth, 20).build()); |
| 78 | + |
| 79 | + addRenderableWidget( |
| 80 | + Button.builder(Component.literal("Load Paste"), b -> loadPaste()) |
| 81 | + .bounds(buttonX + (buttonWidth + gap), buttonY, buttonWidth, 20) |
| 82 | + .build()); |
| 83 | + |
| 84 | + addRenderableWidget(Button |
| 85 | + .builder(Component.literal("Use Built-in"), b -> useBuiltIn()) |
| 86 | + .bounds(buttonX + (buttonWidth + gap) * 2, buttonY, buttonWidth, 20) |
| 87 | + .build()); |
| 88 | + |
| 89 | + addRenderableWidget(Button |
| 90 | + .builder(Component.literal("Back"), |
| 91 | + b -> minecraft.gui.setScreen(prevScreen)) |
| 92 | + .bounds(buttonX + (buttonWidth + gap) * 3, buttonY, buttonWidth, 20) |
| 93 | + .build()); |
| 94 | + |
| 95 | + status = ShadertoyBackgroundManager.hasCustomShader() |
| 96 | + ? "Custom shader cache is active." : "Using built-in shader."; |
| 97 | + } |
| 98 | + |
| 99 | + private void loadUrl() |
| 100 | + { |
| 101 | + if(loading) |
| 102 | + return; |
| 103 | + |
| 104 | + String url = urlBox.getValue().trim(); |
| 105 | + if(url.isEmpty()) |
| 106 | + { |
| 107 | + status = "Paste a Shadertoy URL first."; |
| 108 | + return; |
| 109 | + } |
| 110 | + |
| 111 | + loading = true; |
| 112 | + status = "Downloading and converting shader..."; |
| 113 | + Thread worker = new Thread(() -> { |
| 114 | + String result; |
| 115 | + try |
| 116 | + { |
| 117 | + result = ShadertoyBackgroundManager.importFromUrl(url); |
| 118 | + }catch(Exception e) |
| 119 | + { |
| 120 | + result = "Failed: " + e.getMessage(); |
| 121 | + } |
| 122 | + |
| 123 | + String finalResult = result; |
| 124 | + minecraft.execute(() -> { |
| 125 | + loading = false; |
| 126 | + status = finalResult; |
| 127 | + if(!finalResult.startsWith("Failed")) |
| 128 | + codeBox |
| 129 | + .setValue(ShadertoyBackgroundManager.loadRawShader()); |
| 130 | + }); |
| 131 | + }, "Wurst Shadertoy Importer"); |
| 132 | + worker.setDaemon(true); |
| 133 | + worker.start(); |
| 134 | + } |
| 135 | + |
| 136 | + private void loadPaste() |
| 137 | + { |
| 138 | + if(loading) |
| 139 | + return; |
| 140 | + |
| 141 | + String source = codeBox.getValue(); |
| 142 | + if(source.isBlank()) |
| 143 | + { |
| 144 | + status = "Paste Shadertoy code first."; |
| 145 | + return; |
| 146 | + } |
| 147 | + |
| 148 | + loading = true; |
| 149 | + status = "Converting pasted shader..."; |
| 150 | + Thread worker = new Thread(() -> { |
| 151 | + String result; |
| 152 | + try |
| 153 | + { |
| 154 | + result = ShadertoyBackgroundManager.importFromSource(source); |
| 155 | + }catch(Exception e) |
| 156 | + { |
| 157 | + result = "Failed: " + e.getMessage(); |
| 158 | + } |
| 159 | + |
| 160 | + String finalResult = result; |
| 161 | + minecraft.execute(() -> { |
| 162 | + loading = false; |
| 163 | + status = finalResult; |
| 164 | + }); |
| 165 | + }, "Wurst Shadertoy Paste Importer"); |
| 166 | + worker.setDaemon(true); |
| 167 | + worker.start(); |
| 168 | + } |
| 169 | + |
| 170 | + private void useBuiltIn() |
| 171 | + { |
| 172 | + try |
| 173 | + { |
| 174 | + ShadertoyBackgroundManager.clearCustomShader(); |
| 175 | + status = "Switched back to built-in shader."; |
| 176 | + }catch(Exception e) |
| 177 | + { |
| 178 | + status = "Failed to clear custom shader: " + e.getMessage(); |
| 179 | + } |
| 180 | + } |
| 181 | + |
| 182 | + @Override |
| 183 | + public void onClose() |
| 184 | + { |
| 185 | + minecraft.gui.setScreen(prevScreen); |
| 186 | + } |
| 187 | + |
| 188 | + @Override |
| 189 | + public void extractRenderState(GuiGraphicsExtractor context, int mouseX, |
| 190 | + int mouseY, float partialTicks) |
| 191 | + { |
| 192 | + context.fillGradient(0, 0, width, height, 0xDA10131B, 0xE0121B29); |
| 193 | + |
| 194 | + context.centeredText(font, "Import Shadertoy Background", width / 2, 16, |
| 195 | + CommonColors.WHITE); |
| 196 | + context.centeredText(font, |
| 197 | + "Use a URL with an API key, or paste raw single-pass mainImage() code below.", |
| 198 | + width / 2, 30, CommonColors.LIGHT_GRAY); |
| 199 | + |
| 200 | + context.text(font, "Shadertoy URL", urlBox.getX(), urlBox.getY() - 12, |
| 201 | + CommonColors.LIGHT_GRAY); |
| 202 | + context.text(font, "Raw Shadertoy Code", codeBox.getX(), |
| 203 | + codeBox.getY() - 12, CommonColors.LIGHT_GRAY); |
| 204 | + |
| 205 | + for(var renderable : renderables) |
| 206 | + renderable.extractRenderState(context, mouseX, mouseY, |
| 207 | + partialTicks); |
| 208 | + |
| 209 | + int statusColor = status.startsWith("Failed") ? WurstColors.LIGHT_RED |
| 210 | + : CommonColors.LIGHT_GRAY; |
| 211 | + context.centeredText(font, status, width / 2, height - 42, statusColor); |
| 212 | + context.centeredText(font, |
| 213 | + "URL import uses -Dwurst.shadertoyApiKey=<your key> when set; paste import works without a key.", |
| 214 | + width / 2, height - 28, CommonColors.LIGHT_GRAY); |
| 215 | + context.centeredText(font, |
| 216 | + "Multipass/audio/video/cubemap Shadertoys are still unsupported.", |
| 217 | + width / 2, height - 16, CommonColors.LIGHT_GRAY); |
| 218 | + } |
| 219 | +} |
0 commit comments