Skip to content

Commit 6c90f18

Browse files
emyfopsbladektAvanatikerblade1234567Blade-kt
authored
[1.20.4] Feat: New gui, texture api and buffer api (#98)
Related: #79 #78 This pull request is the result of 7 months of hard work A new maintainable click gui api was implemented A new setting layout builder was added to create custom gui elements such as buttons, sliders, etc. The opengl common buffer was reworked to reduce overhead The texture uploading process was simplified for the end user by providing a new `TextureOwner` object that keeps track of all the textures created in a given scope The functions to upload textures are as follow: - `fun Any.upload() = ...` - `fun Any.uploadGif() = ...` - `fun Any.uploadField() = ...` A new AnimatedTexture class was added for gif rendering The texture base class was extended to correctly handle other pixel formats such as BGRA, RG, RED and provide mappings to mojang's native image formats The pixel buffer was reworked according to the buffer api Both fonts, text and emojis, were reworked according to the texture api A new [SDF texture](https://steamcdn-a.akamaihd.net/apps/valve/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf) (also known as alpha-tested magnification) was implemented in order to display clearer characters at any scale or transformation There are many breaking changes that need to be corrected before merging This pull request is NOT ready to be merged <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added animated texture support for GIFs with a new HUD module. - Introduced a Map Downloader that saves in‑game maps as PNG images. - **UI Enhancements** - Revamped Click GUI with updated layout, new settings (title bar height, module size, round radius, etc.) and improved visual customization. - Enhanced font and emoji rendering using new shader techniques for smoother, sharper text. - **Improvements** - Streamlined module list and HUD rendering for a cleaner display. - Improved interactive animations and slider precision. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Blade-gl <bladecore.kt@gmail.com> Co-authored-by: Constructor <fractalminds@protonmail.com> Co-authored-by: blade <blade@gmail.com> Co-authored-by: blade.kt <kurtasanoff@gmail.com>
1 parent a66f57e commit 6c90f18

File tree

142 files changed

+5274
-3838
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

142 files changed

+5274
-3838
lines changed

common/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@
1919

2020
import com.lambda.event.EventFlow;
2121
import com.lambda.event.events.InventoryEvent;
22+
import com.lambda.module.modules.render.NoRender;
2223
import net.minecraft.client.network.ClientPlayNetworkHandler;
2324
import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket;
2425
import net.minecraft.network.packet.s2c.play.UpdateSelectedSlotS2CPacket;
2526
import org.spongepowered.asm.mixin.Mixin;
2627
import org.spongepowered.asm.mixin.injection.At;
2728
import org.spongepowered.asm.mixin.injection.Inject;
29+
import org.spongepowered.asm.mixin.injection.Redirect;
2830
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
2931

3032
@Mixin(ClientPlayNetworkHandler.class)
@@ -38,4 +40,9 @@ private void onUpdateSelectedSlot(UpdateSelectedSlotS2CPacket packet, CallbackIn
3840
private void onScreenHandlerSlotUpdate(ScreenHandlerSlotUpdateS2CPacket packet, CallbackInfo ci) {
3941
EventFlow.post(new InventoryEvent.SlotUpdate(packet.getSyncId(), packet.getRevision(), packet.getSlot(), packet.getStack()));
4042
}
43+
44+
@Redirect(method = "onServerMetadata", at = @At(value = "FIELD", target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;displayedUnsecureChatWarning:Z", ordinal = 0))
45+
public boolean onServerMetadata(ClientPlayNetworkHandler clientPlayNetworkHandler) {
46+
return NoRender.getNoChatVerificationToast();
47+
}
4148
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2025 Lambda
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.lambda.mixin.render;
19+
20+
import com.lambda.module.modules.client.LambdaMoji;
21+
import net.minecraft.client.font.TextRenderer;
22+
import net.minecraft.client.gui.DrawContext;
23+
import net.minecraft.client.gui.hud.ChatHud;
24+
import net.minecraft.text.OrderedText;
25+
import org.spongepowered.asm.mixin.Mixin;
26+
import org.spongepowered.asm.mixin.injection.At;
27+
import org.spongepowered.asm.mixin.injection.Redirect;
28+
29+
@Mixin(ChatHud.class)
30+
public class ChatHudMixin {
31+
@Redirect(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawTextWithShadow(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/text/OrderedText;III)I"))
32+
int redirectRenderCall(DrawContext instance, TextRenderer textRenderer, OrderedText text, int x, int y, int color) {
33+
return instance.drawTextWithShadow(textRenderer, LambdaMoji.INSTANCE.parse(text, x, y, color), 0, y, 16777215 + (color << 24));
34+
}
35+
}

common/src/main/java/com/lambda/mixin/render/ChatInputSuggestorMixin.java

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,47 @@
1717

1818
package com.lambda.mixin.render;
1919

20+
import com.google.common.base.Strings;
2021
import com.lambda.command.CommandManager;
22+
import com.lambda.graphics.renderer.gui.font.core.LambdaAtlas;
23+
import com.lambda.module.modules.client.LambdaMoji;
24+
import com.lambda.module.modules.client.RenderSettings;
2125
import com.mojang.brigadier.CommandDispatcher;
26+
import com.mojang.brigadier.suggestion.Suggestions;
27+
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
2228
import net.minecraft.client.gui.screen.ChatInputSuggestor;
2329
import net.minecraft.client.gui.widget.TextFieldWidget;
2430
import net.minecraft.client.network.ClientPlayNetworkHandler;
2531
import net.minecraft.command.CommandSource;
32+
import org.jetbrains.annotations.Nullable;
2633
import org.spongepowered.asm.mixin.Final;
2734
import org.spongepowered.asm.mixin.Mixin;
2835
import org.spongepowered.asm.mixin.Shadow;
36+
import org.spongepowered.asm.mixin.Unique;
2937
import org.spongepowered.asm.mixin.injection.At;
38+
import org.spongepowered.asm.mixin.injection.Inject;
3039
import org.spongepowered.asm.mixin.injection.ModifyVariable;
3140
import org.spongepowered.asm.mixin.injection.Redirect;
41+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
42+
43+
import java.util.concurrent.CompletableFuture;
44+
import java.util.regex.Matcher;
45+
import java.util.regex.Pattern;
46+
import java.util.stream.Stream;
3247

3348
@Mixin(ChatInputSuggestor.class)
34-
public class ChatInputSuggestorMixin {
49+
public abstract class ChatInputSuggestorMixin {
3550

3651
@Shadow
3752
@Final
3853
TextFieldWidget textField;
3954

55+
@Shadow
56+
private @Nullable CompletableFuture<Suggestions> pendingSuggestions;
57+
58+
@Shadow
59+
public abstract void show(boolean narrateFirstSuggestion);
60+
4061
@ModifyVariable(method = "refresh", at = @At(value = "STORE"), index = 3)
4162
private boolean refreshModify(boolean showCompletions) {
4263
return CommandManager.INSTANCE.isCommand(textField.getText());
@@ -46,4 +67,55 @@ private boolean refreshModify(boolean showCompletions) {
4667
private CommandDispatcher<CommandSource> refreshRedirect(ClientPlayNetworkHandler instance) {
4768
return CommandManager.INSTANCE.currentDispatcher(textField.getText());
4869
}
70+
71+
@Inject(method = "refresh", at = @At("TAIL"))
72+
private void refreshEmojiSuggestion(CallbackInfo ci) {
73+
if (!LambdaMoji.INSTANCE.isEnabled() ||
74+
!LambdaMoji.INSTANCE.getSuggestions()) return;
75+
76+
String typing = textField.getText();
77+
78+
// Don't suggest emojis in commands
79+
if (CommandManager.INSTANCE.isCommand(typing) ||
80+
CommandManager.INSTANCE.isLambdaCommand(typing)) return;
81+
82+
int cursor = textField.getCursor();
83+
String textToCursor = typing.substring(0, cursor);
84+
if (textToCursor.isEmpty()) return;
85+
86+
// Most right index at the left of the regex expression
87+
int start = neoLambda$getLastColon(textToCursor);
88+
if (start == -1) return;
89+
90+
String emojiString = typing.substring(start + 1);
91+
92+
Stream<String> results = LambdaAtlas.INSTANCE.getKeys(RenderSettings.INSTANCE.getEmojiFont())
93+
.keySet().stream()
94+
.filter(s -> s.startsWith(emojiString))
95+
.map(s -> s + ":");
96+
97+
pendingSuggestions = CommandSource.suggestMatching(results, new SuggestionsBuilder(textToCursor, start + 1));
98+
pendingSuggestions.thenRun(() -> {
99+
if (!pendingSuggestions.isDone()) return;
100+
101+
show(false);
102+
});
103+
}
104+
105+
@Unique
106+
private static final Pattern COLON_PATTERN = Pattern.compile("(:[a-zA-Z0-9_]+)");
107+
108+
@Unique
109+
private int neoLambda$getLastColon(String input) {
110+
if (Strings.isNullOrEmpty(input)) return -1;
111+
112+
int i = -1;
113+
Matcher matcher = COLON_PATTERN.matcher(input);
114+
115+
while (matcher.find()) {
116+
i = matcher.start();
117+
}
118+
119+
return i;
120+
}
49121
}

common/src/main/java/com/lambda/mixin/render/ChatScreenMixin.java

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -17,65 +17,15 @@
1717

1818
package com.lambda.mixin.render;
1919

20-
import com.lambda.Lambda;
2120
import com.lambda.command.CommandManager;
22-
import com.lambda.graphics.renderer.gui.font.FontRenderer;
23-
import com.lambda.graphics.renderer.gui.font.LambdaEmoji;
24-
import com.lambda.graphics.renderer.gui.font.glyph.GlyphInfo;
25-
import com.lambda.module.modules.client.LambdaMoji;
26-
import com.lambda.util.math.Vec2d;
27-
import kotlin.Pair;
28-
import kotlin.ranges.IntRange;
2921
import net.minecraft.client.gui.screen.ChatScreen;
3022
import org.spongepowered.asm.mixin.Mixin;
3123
import org.spongepowered.asm.mixin.injection.At;
3224
import org.spongepowered.asm.mixin.injection.Inject;
33-
import org.spongepowered.asm.mixin.injection.ModifyArg;
3425
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
3526

36-
import java.util.ArrayList;
37-
import java.util.Collections;
38-
import java.util.List;
39-
4027
@Mixin(ChatScreen.class)
4128
public abstract class ChatScreenMixin {
42-
@ModifyArg(method = "sendMessage", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;sendChatMessage(Ljava/lang/String;)V"), index = 0)
43-
private String modifyChatText(String chatText) {
44-
if (LambdaMoji.INSTANCE.isDisabled()) return chatText;
45-
46-
List<Pair<GlyphInfo, IntRange>> emojis = FontRenderer.Companion.parseEmojis(chatText, LambdaEmoji.Twemoji);
47-
Collections.reverse(emojis);
48-
49-
List<String> pushEmojis = new ArrayList<>();
50-
List<Vec2d> pushPositions = new ArrayList<>();
51-
52-
for (Pair<GlyphInfo, IntRange> emoji : emojis) {
53-
String emojiString = chatText.substring(emoji.getSecond().getStart() + 1, emoji.getSecond().getEndInclusive());
54-
if (LambdaEmoji.Twemoji.get(emojiString) == null)
55-
continue;
56-
57-
// Because the width of a char is bigger than an emoji
58-
// we can simply replace the matches string by a space
59-
// and render it after the text
60-
chatText = chatText.substring(0, emoji.getSecond().getStart()) + " " + chatText.substring(emoji.getSecond().getEndInclusive() + 1);
61-
62-
// We cannot retain the position in the future, but we can
63-
// assume that every time you send a message the height of
64-
// the position will change by the height of the glyph
65-
// The positions are from the top left corner of the screen
66-
int x = Lambda.getMc().textRenderer.getWidth(chatText.substring(0, emoji.getSecond().getStart()));
67-
int y = Lambda.getMc().textRenderer.fontHeight;
68-
69-
pushEmojis.add(String.format(":%s:", emojiString));
70-
pushPositions.add(new Vec2d(x, y));
71-
}
72-
73-
// Not optimal because it has to parse the emoji again but who cares
74-
LambdaMoji.INSTANCE.add(pushEmojis, pushPositions);
75-
76-
return chatText;
77-
}
78-
7929
@Inject(method = "sendMessage", at = @At("HEAD"), cancellable = true)
8030
void sendMessageInject(String chatText, boolean addToHistory, CallbackInfoReturnable<Boolean> cir) {
8131
if (!CommandManager.INSTANCE.isLambdaCommand(chatText)) return;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2025 Lambda
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.lambda.mixin.render;
19+
20+
import com.lambda.Lambda;
21+
import com.lambda.module.modules.render.MapPreview;
22+
import net.minecraft.client.MinecraftClient;
23+
import net.minecraft.client.font.TextRenderer;
24+
import net.minecraft.client.gui.DrawContext;
25+
import net.minecraft.client.gui.screen.Screen;
26+
import net.minecraft.client.gui.screen.ingame.HandledScreen;
27+
import net.minecraft.client.gui.tooltip.HoveredTooltipPositioner;
28+
import net.minecraft.client.gui.tooltip.TooltipBackgroundRenderer;
29+
import net.minecraft.client.gui.tooltip.TooltipComponent;
30+
import net.minecraft.client.gui.tooltip.TooltipPositioner;
31+
import net.minecraft.client.item.TooltipData;
32+
import net.minecraft.item.ItemStack;
33+
import net.minecraft.item.Items;
34+
import net.minecraft.text.Text;
35+
import org.spongepowered.asm.mixin.Final;
36+
import org.spongepowered.asm.mixin.Mixin;
37+
import org.spongepowered.asm.mixin.Shadow;
38+
import org.spongepowered.asm.mixin.injection.At;
39+
import org.spongepowered.asm.mixin.injection.Inject;
40+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
41+
42+
import java.util.List;
43+
import java.util.Optional;
44+
import java.util.stream.Collectors;
45+
46+
@Mixin(DrawContext.class)
47+
public abstract class DrawContextMixin {
48+
@Shadow protected abstract void drawTooltip(TextRenderer textRenderer, List<TooltipComponent> components, int x, int y, TooltipPositioner positioner);
49+
50+
@Inject(method = "drawTooltip(Lnet/minecraft/client/font/TextRenderer;Ljava/util/List;Ljava/util/Optional;II)V", at = @At("HEAD"), cancellable = true)
51+
void drawItemTooltip(TextRenderer textRenderer, List<Text> text, Optional<TooltipData> data, int x, int y, CallbackInfo ci) {
52+
List<TooltipComponent> list = text.stream().map(Text::asOrderedText).map(TooltipComponent::of).collect(Collectors.toList());
53+
data.ifPresent(datax -> list.add(1, TooltipComponent.of(datax)));
54+
55+
var screen = (HandledScreen) Lambda.getMc().currentScreen;
56+
if (screen.focusedSlot != null) {
57+
var stack = screen.focusedSlot.getStack();
58+
if (stack.isOf(Items.FILLED_MAP)) list.add(1, new MapPreview.MapComponent(stack));
59+
}
60+
61+
drawTooltip(textRenderer, list, x, y, HoveredTooltipPositioner.INSTANCE);
62+
ci.cancel();
63+
}
64+
}

common/src/main/java/com/lambda/mixin/render/SplashOverlayMixin.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
package com.lambda.mixin.render;
1919

20-
import com.lambda.util.LambdaResource;
20+
import com.lambda.util.LambdaResourceKt;
2121
import net.minecraft.client.gui.screen.SplashOverlay;
2222
import net.minecraft.client.texture.ResourceTexture;
2323
import net.minecraft.resource.DefaultResourcePack;
@@ -62,7 +62,7 @@ public LogoTextureMixin(Identifier location) {
6262

6363
@Redirect(method = "loadTextureData", at = @At(value = "INVOKE", target = "Lnet/minecraft/resource/DefaultResourcePack;open(Lnet/minecraft/resource/ResourceType;Lnet/minecraft/util/Identifier;)Lnet/minecraft/resource/InputSupplier;"))
6464
InputSupplier<InputStream> loadTextureData(DefaultResourcePack instance, ResourceType type, Identifier id) {
65-
return () -> new LambdaResource("textures/lambda_banner.png").getStream();
65+
return () -> LambdaResourceKt.getStream("textures/lambda_banner.png");
6666
}
6767
}
6868
}

common/src/main/kotlin/com/lambda/Lambda.kt

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,7 @@ package com.lambda
2020
import com.google.gson.Gson
2121
import com.google.gson.GsonBuilder
2222
import com.lambda.config.serializer.*
23-
import com.lambda.config.serializer.gui.CustomModuleWindowSerializer
24-
import com.lambda.config.serializer.gui.ModuleTagSerializer
25-
import com.lambda.config.serializer.gui.TagWindowSerializer
2623
import com.lambda.core.Loader
27-
import com.lambda.gui.impl.clickgui.windows.tag.CustomModuleWindow
28-
import com.lambda.gui.impl.clickgui.windows.tag.TagWindow
2924
import com.lambda.module.tag.ModuleTag
3025
import com.lambda.util.KeyCode
3126
import com.mojang.authlib.GameProfile
@@ -54,9 +49,6 @@ object Lambda {
5449

5550
val gson: Gson = GsonBuilder()
5651
.setPrettyPrinting()
57-
.registerTypeAdapter(ModuleTag::class.java, ModuleTagSerializer)
58-
.registerTypeAdapter(CustomModuleWindow::class.java, CustomModuleWindowSerializer)
59-
.registerTypeAdapter(TagWindow::class.java, TagWindowSerializer)
6052
.registerTypeAdapter(KeyCode::class.java, KeyCodeSerializer)
6153
.registerTypeAdapter(Color::class.java, ColorSerializer)
6254
.registerTypeAdapter(BlockPos::class.java, BlockPosSerializer)

common/src/main/kotlin/com/lambda/config/Configurable.kt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,14 @@ import com.google.gson.reflect.TypeToken
2323
import com.lambda.Lambda
2424
import com.lambda.Lambda.LOG
2525
import com.lambda.config.settings.CharSetting
26+
import com.lambda.config.settings.FunctionSetting
2627
import com.lambda.config.settings.StringSetting
2728
import com.lambda.config.settings.collections.ListSetting
2829
import com.lambda.config.settings.collections.MapSetting
2930
import com.lambda.config.settings.collections.SetSetting
3031
import com.lambda.config.settings.comparable.BooleanSetting
3132
import com.lambda.config.settings.comparable.EnumSetting
32-
import com.lambda.config.settings.complex.BlockPosSetting
33-
import com.lambda.config.settings.complex.BlockSetting
34-
import com.lambda.config.settings.complex.ColorSetting
35-
import com.lambda.config.settings.complex.KeyBindSetting
33+
import com.lambda.config.settings.complex.*
3634
import com.lambda.config.settings.numeric.*
3735
import com.lambda.util.Communication.logError
3836
import com.lambda.util.KeyCode
@@ -173,7 +171,6 @@ abstract class Configurable(
173171
* @param name The unique identifier for the setting.
174172
* @param defaultValue The default [List] value of type [T] for the setting.
175173
* @param description A brief explanation of the setting's purpose and behavior.
176-
* @param hackDelegates A flag that determines whether the setting should be serialized with the default value.
177174
* @param visibility A lambda expression that determines the visibility status of the setting.
178175
*
179176
* ```kotlin
@@ -204,7 +201,6 @@ abstract class Configurable(
204201
* @param name The unique identifier for the setting.
205202
* @param defaultValue The default [Map] value of type [K] and [V] for the setting.
206203
* @param description A brief explanation of the setting's purpose and behavior.
207-
* @param hackDelegates A flag that determines whether the setting should be serialized with the default value.
208204
* @param visibility A lambda expression that determines the visibility status of the setting.
209205
*
210206
* ```kotlin
@@ -424,4 +420,11 @@ abstract class Configurable(
424420
description: String = "",
425421
visibility: () -> Boolean = { true },
426422
) = BlockSetting(name, defaultValue, description, visibility).register()
423+
424+
fun setting(
425+
name: String,
426+
defaultValue: () -> Unit,
427+
description: String = "",
428+
visibility: () -> Boolean = { true }
429+
) = FunctionSetting(name, defaultValue, description, visibility).register()
427430
}

0 commit comments

Comments
 (0)