add GUIScale slider for the client's own hud and clickgui

new 0.5x-2.0x slider in the ClickGui settings scales the clickgui, the
hud editor panel and every hud module about its own anchor. mouse
coordinates are divided back into layout units on entry so dragging,
sliders and hover checks keep working, hud hit boxes and drag clamping
use the scaled extents, and the font rasterizer follows the ui scale so
text stays one texel per screen pixel at any scale factor
This commit is contained in:
froslanz
2026-10-22 12:26:26 -04:00
parent 1f69a3ecb4
commit 6ab84a1051
5 changed files with 80 additions and 27 deletions
@@ -4,6 +4,7 @@ import cc.fros.Fros;
import cc.fros.features.Feature;
import cc.fros.features.gui.items.buttons.ModuleButton;
import cc.fros.features.modules.Module;
import cc.fros.features.modules.client.ClickGuiModule;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.Screen;
@@ -84,27 +85,39 @@ public class FrosGui extends Screen {
}
context.fill(0, 0, context.guiWidth(), context.guiHeight(), new Color(0, 0, 0, 120).hashCode());
this.widgets.forEach(components -> components.drawScreen(context, mouseX, mouseY, delta));
// scale the whole clickgui; widgets work in layout units so mouse
// coordinates are divided back down before they reach them
float scale = ClickGuiModule.scale();
int layoutX = (int) (mouseX / scale);
int layoutY = (int) (mouseY / scale);
context.pose().pushMatrix();
context.pose().scale(scale, scale);
this.widgets.forEach(components -> components.drawScreen(context, layoutX, layoutY, delta));
context.pose().popMatrix();
}
@Override
public boolean mouseClicked(MouseButtonEvent click, boolean doubled) {
this.widgets.forEach(components -> components.mouseClicked((int) click.x(), (int) click.y(), click.button()));
float scale = ClickGuiModule.scale();
this.widgets.forEach(components -> components.mouseClicked((int) (click.x() / scale), (int) (click.y() / scale), click.button()));
return super.mouseClicked(click, doubled);
}
@Override
public boolean mouseReleased(MouseButtonEvent click) {
this.widgets.forEach(components -> components.mouseReleased((int) click.x(), (int) click.y(), click.button()));
float scale = ClickGuiModule.scale();
this.widgets.forEach(components -> components.mouseReleased((int) (click.x() / scale), (int) (click.y() / scale), click.button()));
return super.mouseReleased(click);
}
@Override
public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) {
int step = Math.max(1, Math.round((float) (10 / ClickGuiModule.scale())));
if (verticalAmount < 0) {
this.widgets.forEach(component -> component.setY(component.getY() - 10));
this.widgets.forEach(component -> component.setY(component.getY() - step));
} else if (verticalAmount > 0) {
this.widgets.forEach(component -> component.setY(component.getY() + 10));
this.widgets.forEach(component -> component.setY(component.getY() + step));
}
return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount);
}
@@ -4,6 +4,7 @@ import cc.fros.Fros;
import cc.fros.features.Feature;
import cc.fros.features.gui.items.buttons.ModuleButton;
import cc.fros.features.modules.Module;
import cc.fros.features.modules.client.ClickGuiModule;
import cc.fros.features.modules.client.HudModule;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.Screen;
@@ -40,27 +41,37 @@ public class HudEditorScreen extends Screen {
@Override
public void render(GuiGraphics context, int mouseX, int mouseY, float delta) {
anyHover = false;
this.components.forEach(component -> component.drawScreen(context, mouseX, mouseY, delta));
// same ui scale treatment as the clickgui
float scale = ClickGuiModule.scale();
int layoutX = (int) (mouseX / scale);
int layoutY = (int) (mouseY / scale);
context.pose().pushMatrix();
context.pose().scale(scale, scale);
this.components.forEach(component -> component.drawScreen(context, layoutX, layoutY, delta));
context.pose().popMatrix();
}
@Override
public boolean mouseClicked(MouseButtonEvent click, boolean doubled) {
this.components.forEach(component -> component.mouseClicked((int) click.x(), (int) click.y(), click.button()));
float scale = ClickGuiModule.scale();
this.components.forEach(component -> component.mouseClicked((int) (click.x() / scale), (int) (click.y() / scale), click.button()));
return super.mouseClicked(click, doubled);
}
@Override
public boolean mouseReleased(MouseButtonEvent click) {
this.components.forEach(component -> component.mouseReleased((int) click.x(), (int) click.y(), click.button()));
float scale = ClickGuiModule.scale();
this.components.forEach(component -> component.mouseReleased((int) (click.x() / scale), (int) (click.y() / scale), click.button()));
return super.mouseReleased(click);
}
@Override
public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) {
int step = Math.max(1, Math.round((float) (10 / ClickGuiModule.scale())));
if (verticalAmount < 0) {
this.components.forEach(component -> component.setY(component.getY() - 10));
this.components.forEach(component -> component.setY(component.getY() - step));
} else if (verticalAmount > 0) {
this.components.forEach(component -> component.setY(component.getY() + 10));
this.components.forEach(component -> component.setY(component.getY() + step));
}
return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount);
}
@@ -19,6 +19,7 @@ public class ClickGuiModule extends Module {
public final Setting<Boolean> minecraftFont = bool("MinecraftFont", false);
public final Setting<String> font = str("Font", "SansSerif");
public final Setting<Integer> fontSize = num("FontSize", 10, 6, 24);
public final Setting<Float> guiScale = num("GUIScale", 1.0f, 0.5f, 2.0f);
public final Setting<String> prefix = str("Prefix", ".");
public final Setting<Color> color = color("Color", 0, 0, 255, 180);
public final Setting<Color> topColor = color("TopColor", 0, 0, 150, 240);
@@ -75,4 +76,9 @@ public class ClickGuiModule extends Module {
public static ClickGuiModule getInstance() {
return INSTANCE;
}
/** ui scale factor applied to the client's own hud and clickgui */
public static float scale() {
return INSTANCE == null ? 1f : Math.max(0.5f, INSTANCE.guiScale.getValue());
}
}
@@ -30,19 +30,36 @@ public abstract class HudModule extends Module {
}
public float getY() {
float scaledHeight = getHeight() * ClickGuiModule.scale();
float heightWithChat = mc.getWindow().getGuiScaledHeight() - 14;
float baseY = mc.getWindow().getGuiScaledHeight() * pos.getValue().y();
float combined = baseY + getHeight();
float combined = baseY + scaledHeight;
if (mc.screen instanceof ChatScreen) {
baseY = Math.min(combined, heightWithChat) - getHeight();
baseY = Math.min(combined, heightWithChat) - scaledHeight;
}
return baseY;
}
@Subscribe
public void onRender2DHud(Render2DEvent e) {
render(e);
float scale = ClickGuiModule.scale();
if (scale == 1f) {
render(e);
return;
}
// scale the element about its own position anchor so content,
// editor box and hover label all grow together
float anchorX = getX();
float anchorY = getY();
e.getContext().pose().pushMatrix();
e.getContext().pose().translate(anchorX * (1f - scale), anchorY * (1f - scale));
e.getContext().pose().scale(scale, scale);
try {
render(e);
} finally {
e.getContext().pose().popMatrix();
}
}
@Subscribe
@@ -75,10 +92,11 @@ public abstract class HudModule extends Module {
}
if (dragging) {
float scale = ClickGuiModule.scale();
float finalX = Math.min(Math.max(getMouseX() - dragX, 0),
mc.getWindow().getGuiScaledWidth() - width);
mc.getWindow().getGuiScaledWidth() - width * scale);
float finalY = Math.min(Math.max(getMouseY() - dragY, 0),
mc.getWindow().getGuiScaledHeight() - height);
mc.getWindow().getGuiScaledHeight() - height * scale);
pos.setValue(new Vector2f(finalX, finalY)
.div(mc.getWindow().getGuiScaledWidth(), mc.getWindow().getGuiScaledHeight()));
@@ -95,8 +113,10 @@ public abstract class HudModule extends Module {
if (shouldDrawDescription) {
int textWidth = FrosFont.width(getName());
int textHeight = FrosFont.lineHeight();
float scale = ClickGuiModule.scale();
float textX = x + width + 5;
if (textX + textWidth > mc.getWindow().getGuiScaledWidth()) {
// the label is drawn inside the element's scale transform
if (x + (textX + textWidth - x) * scale > mc.getWindow().getGuiScaledWidth()) {
textX = x - 5 - textWidth;
}
FrosFont.draw(e.getContext(), getName(),
@@ -129,9 +149,10 @@ public abstract class HudModule extends Module {
float y = getY();
int mouseX = getMouseX();
int mouseY = getMouseY();
float scale = ClickGuiModule.scale();
return mouseX >= x - 1 && mouseX <= x + width + 1 &&
mouseY >= y - 1 && mouseY <= y + height + 1;
return mouseX >= x - 1 && mouseX <= x + width * scale + 1 &&
mouseY >= y - 1 && mouseY <= y + height * scale + 1;
}
public float getWidth() {
@@ -82,7 +82,7 @@ public final class FrosFont {
private static FontMetrics metrics;
private static String loadedName;
private static float loadedSize = Float.NaN;
private static int loadedScale = 1;
private static float loadedScale = 1;
private static long lastAttempt;
private static int glyphCounter;
@@ -110,14 +110,16 @@ public final class FrosFont {
/** converts a raster pixel measurement to gui units */
private static int toGui(int pixels) {
return (pixels + loadedScale - 1) / loadedScale;
return (int) Math.ceil(pixels / (double) loadedScale);
}
private static int currentScale() {
private static float currentScale() {
try {
return Math.max(1, (int) Math.round(mc.getWindow().getGuiScale()));
// one raster pixel per screen pixel, following both mc's gui scale
// and the client's own ui scale slider
return Math.max(0.5f, (float) (mc.getWindow().getGuiScale() * ClickGuiModule.scale()));
} catch (Exception e) {
return 1;
return 1f;
}
}
@@ -154,7 +156,7 @@ public final class FrosFont {
String name = gui == null || vanilla ? "" : gui.font.getValue();
int size = gui == null ? 10 : gui.fontSize.getValue();
if (name == null) name = "";
int scale = currentScale();
float scale = currentScale();
boolean changed = !Objects.equals(name, loadedName) || size != loadedSize || scale != loadedScale;
if (!changed) {
@@ -166,7 +168,7 @@ public final class FrosFont {
reset(name, size, scale, changed);
}
private static void reset(String name, float size, int scale, boolean verbose) {
private static void reset(String name, float size, float scale, boolean verbose) {
loadedName = name;
loadedSize = size;
loadedScale = scale;
@@ -312,7 +314,7 @@ public final class FrosFont {
String plain = stripCodes(text);
if (plain.isEmpty()) return null;
int pad = loadedScale;
int pad = Math.max(1, Math.round(loadedScale));
int width = Math.max(1, metrics.stringWidth(plain));
int height = metrics.getHeight();
int imageWidth = width + 2 * pad;
@@ -345,7 +347,7 @@ public final class FrosFont {
}
private static void drawSegments(Graphics2D graphics, String text, int baseColor) {
int baseline = metrics.getAscent() + loadedScale;
int baseline = metrics.getAscent() + Math.round(loadedScale);
int cursor = 1;
int current = baseColor;
int segmentStart = 0;