Skip to content

Commit cbd2436

Browse files
committed
Shader Presets
1 parent be876fd commit cbd2436

6 files changed

Lines changed: 410 additions & 12 deletions

File tree

src/main/java/net/wurstclient/options/CustomShadertoyBackgroundScreen.java

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public final class CustomShadertoyBackgroundScreen extends Screen
2525
private final Screen prevScreen;
2626
private EditBox urlBox;
2727
private MultiLineEditBox codeBox;
28+
private Button savePresetButton;
2829
private String status = "";
2930
private boolean loading;
3031

@@ -56,7 +57,7 @@ protected void init()
5657
addRenderableWidget(urlBox);
5758

5859
int editorY = urlY + 48;
59-
int editorHeight = Math.max(120, height - editorY - 96);
60+
int editorHeight = Math.max(120, height - editorY - 144);
6061
codeBox = MultiLineEditBox.builder().setX(left).setY(editorY)
6162
.setPlaceholder(Component.literal(
6263
"Paste raw Shadertoy code here, including mainImage(...)."))
@@ -69,7 +70,7 @@ protected void init()
6970
int buttonY = editorY + editorHeight + 10;
7071
int gap = 6;
7172
int buttonWidth = 112;
72-
int totalWidth = buttonWidth * 4 + gap * 3;
73+
int totalWidth = buttonWidth * 3 + gap * 2;
7374
int buttonX = width / 2 - totalWidth / 2;
7475

7576
addRenderableWidget(
@@ -86,10 +87,21 @@ protected void init()
8687
.bounds(buttonX + (buttonWidth + gap) * 2, buttonY, buttonWidth, 20)
8788
.build());
8889

90+
int secondRowY = buttonY + 24;
91+
addRenderableWidget(
92+
Button.builder(Component.literal("Load Preset"), b -> loadPreset())
93+
.bounds(buttonX, secondRowY, buttonWidth, 20).build());
94+
95+
savePresetButton = addRenderableWidget(Button
96+
.builder(Component.literal("Save Preset"), b -> savePreset())
97+
.bounds(buttonX + (buttonWidth + gap), secondRowY, buttonWidth, 20)
98+
.build());
99+
89100
addRenderableWidget(Button
90101
.builder(Component.literal("Back"),
91102
b -> minecraft.gui.setScreen(prevScreen))
92-
.bounds(buttonX + (buttonWidth + gap) * 3, buttonY, buttonWidth, 20)
103+
.bounds(buttonX + (buttonWidth + gap) * 2, secondRowY, buttonWidth,
104+
20)
93105
.build());
94106

95107
status = ShadertoyBackgroundManager.hasCustomShader()
@@ -179,6 +191,58 @@ private void useBuiltIn()
179191
}
180192
}
181193

194+
private void savePreset()
195+
{
196+
if(loading)
197+
return;
198+
199+
String source = codeBox.getValue();
200+
if(source.isBlank())
201+
{
202+
status = "Load or paste a custom shader before saving a preset.";
203+
return;
204+
}
205+
206+
minecraft.gui.setScreen(new EnterProfileNameScreen(this, name -> {
207+
try
208+
{
209+
status = ShadertoyBackgroundManager.savePreset(name, source);
210+
}catch(Exception e)
211+
{
212+
status = "Failed: " + e.getMessage();
213+
}
214+
}, Component.literal("Save this shader as a preset"),
215+
ShadertoyBackgroundManager::isValidPresetName));
216+
}
217+
218+
private void loadPreset()
219+
{
220+
if(loading)
221+
return;
222+
223+
minecraft.gui.setScreen(new ShadertoyPresetScreen(this));
224+
}
225+
226+
public void reloadFromDisk(String newStatus)
227+
{
228+
if(urlBox != null)
229+
urlBox.setValue(WurstClient.INSTANCE.getOtfs().wurstOptionsOtf
230+
.getTitleScreenShadertoyUrlSetting().getValue());
231+
232+
if(codeBox != null)
233+
codeBox.setValue(ShadertoyBackgroundManager.loadRawShader());
234+
235+
status = newStatus;
236+
}
237+
238+
@Override
239+
public void tick()
240+
{
241+
if(savePresetButton != null)
242+
savePresetButton.active =
243+
ShadertoyBackgroundManager.hasCustomShader() && !loading;
244+
}
245+
182246
@Override
183247
public void onClose()
184248
{
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
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 java.nio.file.Path;
11+
import java.util.Arrays;
12+
import java.util.List;
13+
import java.util.Objects;
14+
15+
import org.lwjgl.glfw.GLFW;
16+
17+
import net.minecraft.client.Minecraft;
18+
import net.minecraft.client.gui.Font;
19+
import net.minecraft.client.gui.GuiGraphicsExtractor;
20+
import net.minecraft.client.gui.components.Button;
21+
import net.minecraft.client.gui.components.ObjectSelectionList;
22+
import net.minecraft.client.gui.components.Renderable;
23+
import net.minecraft.client.gui.screens.ConfirmScreen;
24+
import net.minecraft.client.gui.screens.Screen;
25+
import net.minecraft.client.input.KeyEvent;
26+
import net.minecraft.network.chat.Component;
27+
import net.minecraft.util.CommonColors;
28+
import net.wurstclient.util.ShadertoyBackgroundManager;
29+
import net.wurstclient.util.WurstColors;
30+
31+
public final class ShadertoyPresetScreen extends Screen
32+
{
33+
private final CustomShadertoyBackgroundScreen prevScreen;
34+
35+
private ListGui listGui;
36+
private Button loadButton;
37+
private Button deleteButton;
38+
private String status = "";
39+
private boolean loading;
40+
41+
public ShadertoyPresetScreen(CustomShadertoyBackgroundScreen prevScreen)
42+
{
43+
super(Component.literal("Shadertoy Presets"));
44+
this.prevScreen = prevScreen;
45+
}
46+
47+
@Override
48+
protected void init()
49+
{
50+
listGui = new ListGui(minecraft, this,
51+
ShadertoyBackgroundManager.listPresets());
52+
addWidget(listGui);
53+
54+
loadButton = addRenderableWidget(
55+
Button.builder(Component.literal("Load"), b -> loadSelected())
56+
.bounds(width / 2 - 154, height - 28, 100, 20).build());
57+
58+
deleteButton = addRenderableWidget(
59+
Button.builder(Component.literal("Delete"), b -> deleteSelected())
60+
.bounds(width / 2 - 50, height - 28, 100, 20).build());
61+
62+
addRenderableWidget(Button
63+
.builder(Component.literal("Back"),
64+
b -> minecraft.gui.setScreen(prevScreen))
65+
.bounds(width / 2 + 54, height - 28, 100, 20).build());
66+
67+
status = listGui.children().isEmpty() ? "No saved presets yet."
68+
: "Select a preset to load it.";
69+
}
70+
71+
private void loadSelected()
72+
{
73+
Path path = listGui.getSelectedPath();
74+
if(path == null || loading)
75+
return;
76+
77+
loading = true;
78+
status = "Loading preset...";
79+
Thread worker = new Thread(() -> {
80+
String result;
81+
try
82+
{
83+
result = ShadertoyBackgroundManager.loadPreset(path);
84+
}catch(Exception e)
85+
{
86+
result = "Failed: " + e.getMessage();
87+
}
88+
89+
String finalResult = result;
90+
minecraft.execute(() -> {
91+
loading = false;
92+
if(finalResult.startsWith("Failed"))
93+
{
94+
status = finalResult;
95+
return;
96+
}
97+
98+
prevScreen.reloadFromDisk(finalResult);
99+
minecraft.gui.setScreen(prevScreen);
100+
});
101+
}, "Wurst Shadertoy Preset Loader");
102+
worker.setDaemon(true);
103+
worker.start();
104+
}
105+
106+
private void deleteSelected()
107+
{
108+
Path path = listGui.getSelectedPath();
109+
if(path == null || loading)
110+
return;
111+
112+
String name = ShadertoyBackgroundManager.getPresetDisplayName(path);
113+
minecraft.gui.setScreen(new ConfirmScreen(confirmed -> {
114+
if(!confirmed)
115+
{
116+
minecraft.gui.setScreen(this);
117+
return;
118+
}
119+
120+
try
121+
{
122+
status = ShadertoyBackgroundManager.deletePreset(path);
123+
minecraft.gui.setScreen(new ShadertoyPresetScreen(prevScreen));
124+
}catch(Exception e)
125+
{
126+
status = "Failed: " + e.getMessage();
127+
minecraft.gui.setScreen(this);
128+
}
129+
}, Component.literal("Delete preset '" + name + "'?"),
130+
Component.literal("This cannot be undone.")));
131+
}
132+
133+
@Override
134+
public boolean keyPressed(KeyEvent context)
135+
{
136+
switch(context.key())
137+
{
138+
case GLFW.GLFW_KEY_ENTER:
139+
loadSelected();
140+
break;
141+
142+
case GLFW.GLFW_KEY_DELETE:
143+
deleteSelected();
144+
break;
145+
146+
case GLFW.GLFW_KEY_ESCAPE:
147+
minecraft.gui.setScreen(prevScreen);
148+
break;
149+
}
150+
151+
return super.keyPressed(context);
152+
}
153+
154+
@Override
155+
public void tick()
156+
{
157+
boolean selected = listGui.getSelected() != null;
158+
loadButton.active = selected && !loading;
159+
deleteButton.active = selected && !loading;
160+
}
161+
162+
@Override
163+
public void extractRenderState(GuiGraphicsExtractor context, int mouseX,
164+
int mouseY, float partialTicks)
165+
{
166+
listGui.extractRenderState(context, mouseX, mouseY, partialTicks);
167+
168+
context.centeredText(font, "Load Shadertoy Preset", width / 2, 12,
169+
CommonColors.WHITE);
170+
171+
for(Renderable drawable : renderables)
172+
drawable.extractRenderState(context, mouseX, mouseY, partialTicks);
173+
174+
int statusColor = status.startsWith("Failed") ? WurstColors.LIGHT_RED
175+
: CommonColors.LIGHT_GRAY;
176+
context.centeredText(font, status, width / 2, height - 42, statusColor);
177+
178+
if(loadButton.isHoveredOrFocused() && !loadButton.active && !loading)
179+
context.setComponentTooltipForNextFrame(font,
180+
Arrays.asList(Component.literal("Select a preset first.")),
181+
mouseX, mouseY);
182+
183+
if(deleteButton.isHoveredOrFocused() && !deleteButton.active
184+
&& !loading)
185+
context.setComponentTooltipForNextFrame(font,
186+
Arrays.asList(Component.literal("Select a preset first.")),
187+
mouseX, mouseY);
188+
}
189+
190+
@Override
191+
public boolean shouldCloseOnEsc()
192+
{
193+
return false;
194+
}
195+
196+
private final class Entry
197+
extends ObjectSelectionList.Entry<ShadertoyPresetScreen.Entry>
198+
{
199+
private final Path path;
200+
201+
private Entry(Path path)
202+
{
203+
this.path = Objects.requireNonNull(path);
204+
}
205+
206+
@Override
207+
public Component getNarration()
208+
{
209+
return Component.translatable("narrator.select", "Preset "
210+
+ ShadertoyBackgroundManager.getPresetDisplayName(path));
211+
}
212+
213+
@Override
214+
public void extractContent(GuiGraphicsExtractor context, int mouseX,
215+
int mouseY, boolean hovered, float tickDelta)
216+
{
217+
int x = getContentX();
218+
int y = getContentY();
219+
Font tr = minecraft.font;
220+
221+
String name = ShadertoyBackgroundManager.getPresetDisplayName(path);
222+
context.text(tr, name, x + 8, y + 1, WurstColors.VERY_LIGHT_GRAY);
223+
224+
String relPath =
225+
"" + minecraft.gameDirectory.toPath().relativize(path);
226+
context.text(tr, relPath, x + 8, y + 11, CommonColors.LIGHT_GRAY);
227+
}
228+
}
229+
230+
private final class ListGui
231+
extends ObjectSelectionList<ShadertoyPresetScreen.Entry>
232+
{
233+
private ListGui(Minecraft mc, Screen screen, List<Path> list)
234+
{
235+
super(mc, screen.width, screen.height - 88, 32, 24);
236+
list.stream().map(ShadertoyPresetScreen.Entry::new)
237+
.forEach(this::addEntry);
238+
}
239+
240+
private Path getSelectedPath()
241+
{
242+
ShadertoyPresetScreen.Entry selected = getSelected();
243+
return selected != null ? selected.path : null;
244+
}
245+
}
246+
}

0 commit comments

Comments
 (0)