Compare commits

6 Commits
Author SHA1 Message Date
froslanz f0398c7011 remove the MinecraftFont toggle and always draw text shadows
the toggle existed to switch back to the vanilla font, but disabling it
left every label without a shadow which read as a bug. the setting,
its visibility hooks and FrosFont's vanilla-mode read are gone; Font
and FontSize now always apply, and every label draws its shadow in both
the custom font and the vanilla fallback path. a stale MinecraftFont key
in old configs is ignored by the loader and disappears on next save.
chat and command output keep the untouched vanilla font without shadows
2026-10-22 13:23:25 -04:00
froslanz 7fc42b34d8 darken colored text segments when rasterizing shadows
the shadow pass only darkened the base color, so any segment with an
explicit color code (the watermark's accent from {global}, the slider
value's section-7 gray) punched through at full brightness, drawing a
colored ghost one pixel off the text. shadow rasters now pass the
original color plus a shadow flag, and every segment is darkened at
flush time with the vanilla rule ((c & 0xFCFCFC) >> 2, alpha kept), so
accent text gets a dark accent shadow and gray gets a dark gray one
2026-10-22 13:19:07 -04:00
froslanz 9b6a55d66c snap dragged hud elements to each other and to a grid
while dragging in the hud editor each axis first attracts to the closest
edge or center of any other enabled hud element within 4 gui units, and
otherwise rounds onto a 4px grid so layouts stay tidy. positions are
re-clamped to the screen after snapping and the snap is computed from the
raw pointer position each frame so there is no feedback jitter while
dragging past an alignment
2026-10-22 12:57:02 -04:00
froslanz 3568996138 center clickgui text by the font's cap band instead of vanilla offsets
button labels, panel titles and the color picker's copy/paste text used
y offsets hand-tuned for vanilla's 9px line box, so the custom font's
14px line box rendered text ~4px too low, poking out below 15px buttons
and over the header accent line. FrosFont now measures the distance from
line top to the middle of the cap band (glyph 'A' visual bounds) and all
labels center on that via FrosFont.visualCenter()/Item.textY(). the
vanilla fallback lands on the exact old offsets, so nothing moves when
fonts are disabled, and the legacy getTextOffset() hack is gone
2026-10-22 12:54:00 -04:00
froslanz ebd0a76f0c tie text shadows to the custom font being active
HUD text, clickgui buttons, panel titles and the hud editor labels all
go through FrosFont's default draw now, which renders a drop shadow
only while the custom font is loaded. disabling fonts (MinecraftFont
toggle, or a missing font file falling back) renders everything flat,
matching the vanilla look, in both the hud and the clickgui
2026-10-22 12:27:52 -04:00
froslanz 6ab84a1051 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
2026-10-22 12:26:26 -04:00
14 changed files with 196 additions and 72 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);
}
@@ -133,10 +146,6 @@ public class FrosGui extends Screen {
return this.widgets;
}
public int getTextOffset() {
return -6;
}
public static Color getColorClipboard() {
return colorClipboard;
}
@@ -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);
}
@@ -77,7 +77,8 @@ public class Widget
RenderUtil.rect(context, this.x, headerTop, this.x + this.width, panelBottom, 0x99000000, 1.0f);
context.fill(this.x, headerBottom - 1, this.x + this.width, headerBottom, accent);
drawString(this.getName(), (float) this.x + 3.0f, (float) this.y - 4.0f - (float) FrosGui.getClickGui().getTextOffset(), -1);
drawString(this.getName(), (float) this.x + 3.0f,
(headerTop + headerBottom) / 2f - FrosFont.visualCenter(), -1);
ScissorUtil.enable(context, x, 0, x + width, mc.getWindow().getGuiScaledHeight());
if (this.open) {
@@ -215,6 +216,6 @@ public class Widget
}
protected void drawString(String text, double x, double y, int color) {
FrosFont.draw(context, text, (int) x, (int) y, color, true);
FrosFont.draw(context, text, (int) x, (int) y, color);
}
}
@@ -80,7 +80,12 @@ public class Item
}
protected void drawString(GuiGraphics context, String text, double x, double y, int color) {
FrosFont.draw(context, text, (int) x, (int) y, color, true);
FrosFont.draw(context, text, (int) x, (int) y, color);
}
/** y coordinate that visually centers text inside this item */
protected float textY() {
return this.y + this.height / 2f - FrosFont.visualCenter();
}
public boolean isHovering(int mouseX, int mouseY) {
@@ -1,7 +1,6 @@
package cc.fros.features.gui.items.buttons;
import cc.fros.Fros;
import cc.fros.features.gui.FrosGui;
import cc.fros.features.modules.client.ClickGuiModule;
import cc.fros.features.settings.Bind;
import cc.fros.features.settings.Setting;
@@ -29,10 +28,10 @@ public class BindButton
int color = ClickGuiModule.getInstance().color.getValue().getRGB();
RenderUtil.rect(context, this.x, this.y, this.x + (float) this.width + 7.4f, this.y + (float) this.height - 0.5f, this.getState() ? (!this.isHovering(mouseX, mouseY) ? 0x11555555 : -2007673515) : (!this.isHovering(mouseX, mouseY) ? Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().color.getValue().getAlpha()) : Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha())));
if (this.isListening) {
drawString(context, "Press a Key...", this.x + 2.3f, this.y - 1.7f - (float) FrosGui.getClickGui().getTextOffset(), -1);
drawString(context, "Press a Key...", this.x + 2.3f, textY(), -1);
} else {
String str = KeyboardUtil.getKeyName(setting.getValue());
drawString(context, this.setting.getName() + " " + ChatFormatting.GRAY + str, this.x + 2.3f, this.y - 1.7f - (float) FrosGui.getClickGui().getTextOffset(), this.getState() ? -1 : -5592406);
drawString(context, this.setting.getName() + " " + ChatFormatting.GRAY + str, this.x + 2.3f, textY(), this.getState() ? -1 : -5592406);
}
}
@@ -2,7 +2,6 @@ package cc.fros.features.gui.items.buttons;
import cc.fros.Fros;
import cc.fros.features.gui.FrosGui;
import cc.fros.features.modules.client.ClickGuiModule;
import cc.fros.features.settings.Setting;
import cc.fros.util.render.RenderUtil;
@@ -23,7 +22,7 @@ public class BooleanButton
@Override
public void drawScreen(GuiGraphics context, int mouseX, int mouseY, float partialTicks) {
RenderUtil.rect(context, this.x, this.y, this.x + (float) this.width + 7.4f, this.y + (float) this.height - 0.5f, this.getState() ? (!this.isHovering(mouseX, mouseY) ? Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().color.getValue().getAlpha()) : Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha())) : (!this.isHovering(mouseX, mouseY) ? 0x11555555 : -2007673515));
drawString(context, this.getName(), this.x + 2.3f, this.y - 1.7f - (float) FrosGui.getClickGui().getTextOffset(), this.getState() ? -1 : -5592406);
drawString(context, this.getName(), this.x + 2.3f, textY(), this.getState() ? -1 : -5592406);
}
@Override
@@ -22,7 +22,7 @@ public class Button
@Override
public void drawScreen(GuiGraphics context, int mouseX, int mouseY, float partialTicks) {
RenderUtil.rect(context, this.x, this.y, this.x + (float) this.width, this.y + (float) this.height - 0.5f, this.getState() ? (!this.isHovering(mouseX, mouseY) ? Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().color.getValue().getAlpha()) : Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha())) : (!this.isHovering(mouseX, mouseY) ? 0x11555555 : -2007673515));
drawString(context, this.getName(), this.x + 2.3f, this.y - 2.0f - (float) FrosGui.getClickGui().getTextOffset(), this.getState() ? -1 : -5592406);
drawString(context, this.getName(), this.x + 2.3f, textY(), this.getState() ? -1 : -5592406);
}
@Override
@@ -44,7 +44,7 @@ public class ColorButton extends Button {
this.x + (float) this.width + 5.0f, this.y + (float) this.height - 2.5f,
new Color(currentColor.getRGB(), false).getRGB());
drawString(context, this.getName(), this.x + 2.3f, this.y - 1.7f - (float) FrosGui.getClickGui().getTextOffset(), -1);
drawString(context, this.getName(), this.x + 2.3f, textY(), -1);
if (open) {
float yOffset = this.height + 1;
@@ -121,12 +121,12 @@ public class ColorButton extends Button {
int buttonWidth = availableWidth / 2;
RenderUtil.rect(context, pickerX, this.y + yOffset, pickerX + buttonWidth, this.y + yOffset + 14,
hoveringCopy ? Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha()) : 0x11555555);
drawString(context, "Copy", pickerX + buttonWidth / 2.0 - FrosFont.width("Copy") / 2.0, this.y + yOffset + 3, -1);
drawString(context, "Copy", pickerX + buttonWidth / 2.0 - FrosFont.width("Copy") / 2.0, this.y + yOffset + 7f - FrosFont.visualCenter(), -1);
hoveringCopy = isHoveringArea(mouseX, mouseY, pickerX, this.y + yOffset, pickerX + buttonWidth, this.y + yOffset + 14);
RenderUtil.rect(context, pickerX + buttonWidth + 1, this.y + yOffset, pickerX + buttonWidth * 2 + 1, this.y + yOffset + 14,
hoveringPaste ? Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha()) : 0x11555555);
drawString(context, "Paste", pickerX + buttonWidth + buttonWidth / 2.0 - FrosFont.width("Paste") / 2.0 + 1, this.y + yOffset + 3, -1);
drawString(context, "Paste", pickerX + buttonWidth + buttonWidth / 2.0 - FrosFont.width("Paste") / 2.0 + 1, this.y + yOffset + 7f - FrosFont.visualCenter(), -1);
hoveringPaste = isHoveringArea(mouseX, mouseY, pickerX + buttonWidth + 1, this.y + yOffset, pickerX + buttonWidth * 2 + 1, this.y + yOffset + 14);
}
}
@@ -2,7 +2,6 @@ package cc.fros.features.gui.items.buttons;
import cc.fros.Fros;
import cc.fros.features.gui.FrosGui;
import cc.fros.features.modules.client.ClickGuiModule;
import cc.fros.features.settings.Setting;
import cc.fros.util.render.RenderUtil;
@@ -24,7 +23,7 @@ public class EnumButton
@Override
public void drawScreen(GuiGraphics context, int mouseX, int mouseY, float partialTicks) {
RenderUtil.rect(context, this.x, this.y, this.x + (float) this.width + 7.4f, this.y + (float) this.height - 0.5f, this.getState() ? (!this.isHovering(mouseX, mouseY) ? Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().color.getValue().getAlpha()) : Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha())) : (!this.isHovering(mouseX, mouseY) ? 0x11555555 : -2007673515));
drawString(context, this.setting.getName() + " " + ChatFormatting.GRAY + (this.setting.currentEnumName().equalsIgnoreCase("ABC") ? "ABC" : this.setting.currentEnumName()), this.x + 2.3f, this.y - 1.7f - (float) FrosGui.getClickGui().getTextOffset(), this.getState() ? -1 : -5592406);
drawString(context, this.setting.getName() + " " + ChatFormatting.GRAY + (this.setting.currentEnumName().equalsIgnoreCase("ABC") ? "ABC" : this.setting.currentEnumName()), this.x + 2.3f, textY(), this.getState() ? -1 : -5592406);
}
@Override
@@ -32,7 +32,7 @@ public class Slider
this.dragSetting(mouseX, mouseY);
RenderUtil.rect(context, this.x, this.y, this.x + (float) this.width + 7.4f, this.y + (float) this.height - 0.5f, !(this.dragging || this.isHovering(mouseX, mouseY)) ? 0x11555555 : -2007673515);
RenderUtil.rect(context, this.x, this.y, (this.setting.getValue()).floatValue() <= this.min.floatValue() ? this.x : this.x + ((float) this.width + 7.4f) * this.partialMultiplier(), this.y + (float) this.height - 0.5f, !(this.dragging || this.isHovering(mouseX, mouseY)) ? Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().color.getValue().getAlpha()) : Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha()));
drawString(context, this.getName() + " " + ChatFormatting.GRAY + (this.setting.getValue() instanceof Float ? this.setting.getValue() : Double.valueOf((this.setting.getValue()).doubleValue())), this.x + 2.3f, this.y - 1.7f - (float) FrosGui.getClickGui().getTextOffset(), -1);
drawString(context, this.getName() + " " + ChatFormatting.GRAY + (this.setting.getValue() instanceof Float ? this.setting.getValue() : Double.valueOf((this.setting.getValue()).doubleValue())), this.x + 2.3f, textY(), -1);
}
@Override
@@ -1,7 +1,6 @@
package cc.fros.features.gui.items.buttons;
import cc.fros.Fros;
import cc.fros.features.gui.FrosGui;
import cc.fros.features.modules.client.ClickGuiModule;
import cc.fros.features.settings.Setting;
import cc.fros.util.models.Timer;
@@ -38,9 +37,9 @@ public class StringButton
public void drawScreen(GuiGraphics context, int mouseX, int mouseY, float partialTicks) {
RenderUtil.rect(context, this.x, this.y, this.x + (float) this.width + 7.4f, this.y + (float) this.height - 0.5f, this.getState() ? (!this.isHovering(mouseX, mouseY) ? Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().color.getValue().getAlpha()) : Fros.colorManager.getColorWithAlpha(y, ClickGuiModule.getInstance().topColor.getValue().getAlpha())) : (!this.isHovering(mouseX, mouseY) ? 0x11555555 : -2007673515));
if (this.isListening) {
drawString(context, this.currentString.string() + getIdleSign(), this.x + 2.3f, this.y - 1.7f - (float) FrosGui.getClickGui().getTextOffset(), this.getState() ? -1 : -5592406);
drawString(context, this.currentString.string() + getIdleSign(), this.x + 2.3f, textY(), this.getState() ? -1 : -5592406);
} else {
drawString(context, (this.setting.getName().equals("Buttons") ? "Buttons " : (this.setting.getName().equals("Prefix") ? "Prefix " + ChatFormatting.GRAY : "")) + this.setting.getValue(), this.x + 2.3f, this.y - 1.7f - (float) FrosGui.getClickGui().getTextOffset(), this.getState() ? -1 : -5592406);
drawString(context, (this.setting.getName().equals("Buttons") ? "Buttons " : (this.setting.getName().equals("Prefix") ? "Prefix " + ChatFormatting.GRAY : "")) + this.setting.getValue(), this.x + 2.3f, textY(), this.getState() ? -1 : -5592406);
}
}
@@ -16,9 +16,9 @@ import static cc.fros.features.commands.MessageSignatures.GENERAL;
public class ClickGuiModule extends Module {
private static ClickGuiModule INSTANCE;
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);
@@ -30,8 +30,7 @@ public class ClickGuiModule extends Module {
public ClickGuiModule() {
super("ClickGui", "Opens the ClickGui", Module.Category.CLIENT);
setBind(GLFW.GLFW_KEY_RIGHT_SHIFT);
font.setVisibility(v -> !minecraftFont.getValue());
fontSize.setVisibility(v -> !minecraftFont.getValue() && !font.getValue().isEmpty());
fontSize.setVisibility(v -> !font.getValue().isEmpty());
rainbowHue.setVisibility(v -> rainbow.getValue());
rainbowBrightness.setVisibility(v -> rainbow.getValue());
rainbowSaturation.setVisibility(v -> rainbow.getValue());
@@ -75,4 +74,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());
}
}
@@ -13,9 +13,16 @@ import cc.fros.util.render.RenderUtil;
import net.minecraft.client.gui.screens.ChatScreen;
import org.joml.Vector2f;
import java.util.List;
public abstract class HudModule extends Module {
public final Setting<Vector2f> pos = vec2f("Position", 0.5f, 0.5f);
/** grid snap lines (gui units) for dragged hud elements */
private static final float SNAP_GRID = 4f;
/** how close a dragged element gets to another's edge/center before it attracts */
private static final float SNAP_RANGE = 4f;
private float dragX, dragY, width, height;
private boolean dragging, button;
@@ -30,19 +37,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,12 +99,16 @@ public abstract class HudModule extends Module {
}
if (dragging) {
float finalX = Math.min(Math.max(getMouseX() - dragX, 0),
mc.getWindow().getGuiScaledWidth() - width);
float finalY = Math.min(Math.max(getMouseY() - dragY, 0),
mc.getWindow().getGuiScaledHeight() - height);
float scale = ClickGuiModule.scale();
float maxX = mc.getWindow().getGuiScaledWidth() - width * scale;
float maxY = mc.getWindow().getGuiScaledHeight() - height * scale;
float rawX = Math.min(Math.max(getMouseX() - dragX, 0), maxX);
float rawY = Math.min(Math.max(getMouseY() - dragY, 0), maxY);
// snap to other elements first, grid otherwise, then re-clamp
float snappedX = Math.min(Math.max(snapAxis(rawX, width * scale, true, scale), 0), maxX);
float snappedY = Math.min(Math.max(snapAxis(rawY, height * scale, false, scale), 0), maxY);
pos.setValue(new Vector2f(finalX, finalY)
pos.setValue(new Vector2f(snappedX, snappedY)
.div(mc.getWindow().getGuiScaledWidth(), mc.getWindow().getGuiScaledHeight()));
}
} else {
@@ -95,12 +123,14 @@ 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(),
(int) textX, (int) (y + height / 2f - textHeight / 2f), -1, true);
(int) textX, (int) (y + height / 2f - textHeight / 2f), -1);
HudEditorScreen.getInstance().anyHover = true;
}
@@ -129,9 +159,47 @@ 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;
}
/**
* Drags on to another visible hud element's left/center/right (or
* top/middle/bottom) when within {@link #SNAP_RANGE}; otherwise rounds
* onto the {@link #SNAP_GRID} grid so everything stays tidy.
*/
private float snapAxis(float position, float size, boolean horizontal, float scale) {
float bestDelta = 0f;
boolean aligned = false;
for (HudModule other : hudModules()) {
if (other == this || !other.isEnabled()) continue;
float otherPos = horizontal ? other.getX() : other.getY();
float otherSize = horizontal ? other.getWidth() * scale : other.getHeight() * scale;
float[] mine = {position, position + size / 2f, position + size};
float[] theirs = {otherPos, otherPos + otherSize / 2f, otherPos + otherSize};
for (float mineAnchor : mine) {
for (float theirAnchor : theirs) {
float delta = theirAnchor - mineAnchor;
if (Math.abs(delta) <= SNAP_RANGE && (!aligned || Math.abs(delta) < Math.abs(bestDelta))) {
bestDelta = delta;
aligned = true;
}
}
}
}
if (aligned) return position + bestDelta;
return Math.round(position / SNAP_GRID) * SNAP_GRID;
}
private static List<HudModule> hudModules() {
return Fros.moduleManager.stream()
.filter(module -> module instanceof HudModule)
.map(module -> (HudModule) module)
.toList();
}
public float getWidth() {
+55 -25
View File
@@ -72,7 +72,7 @@ public final class FrosFont {
private static final List<Identifier> PENDING_RELEASE = new ArrayList<>();
private static long lastQueuedAt;
private record Key(int color, String text) {
private record Key(int color, String text, boolean shadow) {
}
private record Glyph(Identifier id, int width, int height) {
@@ -82,7 +82,9 @@ 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;
/** raster pixels from the top of a line box to the middle of the cap band */
private static float visualCenterRaster = 4f;
private static long lastAttempt;
private static int glyphCounter;
@@ -110,19 +112,21 @@ 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;
}
}
public static void draw(GuiGraphics context, String text, int x, int y, int color) {
draw(context, text, x, y, color, false);
draw(context, text, x, y, color, true);
}
public static void draw(GuiGraphics context, String text, int x, int y, int color, boolean shadow) {
@@ -132,29 +136,28 @@ public final class FrosFont {
context.drawString(mc.font, text, x, y, color, shadow);
return;
}
if (shadow) drawGlyphs(context, text, x + 1, y + 1, shadowColor(color));
drawGlyphs(context, text, x, y, color);
if (shadow) drawGlyphs(context, text, x + 1, y + 1, color, true);
drawGlyphs(context, text, x, y, color, false);
}
public static void draw(GuiGraphics context, Component component, int x, int y, int color) {
if (component == null) return;
sync();
if (awtFont == null) {
context.drawString(mc.font, component, x, y, color);
context.drawString(mc.font, component, x, y, color, true);
return;
}
draw(context, toCodeString(component), x, y, color, false);
draw(context, toCodeString(component), x, y, color);
}
// --- loading ---
private static void sync() {
ClickGuiModule gui = ClickGuiModule.getInstance();
boolean vanilla = gui != null && gui.minecraftFont.getValue();
String name = gui == null || vanilla ? "" : gui.font.getValue();
String name = gui == null ? "" : 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 +169,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;
@@ -210,10 +213,33 @@ public final class FrosFont {
Graphics2D graphics = probe.createGraphics();
graphics.setFont(awtFont);
metrics = graphics.getFontMetrics();
// distance from the top of the line box to the middle of the cap band:
// drawString puts the baseline at metrics.getAscent(), and "A" spans
// from the baseline up to the cap line
java.awt.geom.Rectangle2D capBounds = awtFont
.createGlyphVector(graphics.getFontRenderContext(), "A")
.getVisualBounds();
float capHeight = (float) capBounds.getHeight();
if (capHeight <= 0f || capHeight > metrics.getAscent()) {
capHeight = metrics.getAscent();
}
visualCenterRaster = metrics.getAscent() - capHeight / 2f;
graphics.dispose();
if (verbose) LOGGER.info("{} ({}pt)", source, size);
}
/**
* Distance from the top of a line box down to the visual center of its
* text (middle of the cap band), in gui units. lets callers center labels
* by what the eye sees instead of the line box, and behaves the same for
* the custom font and the vanilla fallback.
*/
public static float visualCenter() {
sync();
if (awtFont == null) return mc.font.lineHeight / 2f - 1f;
return visualCenterRaster / loadedScale;
}
/**
* Resolves java's logical fonts (guaranteed on every os) and any font
* family already installed on the system, without needing a file.
@@ -286,12 +312,12 @@ public final class FrosFont {
// --- rasterizing ---
private static void drawGlyphs(GuiGraphics context, String text, int x, int y, int color) {
private static void drawGlyphs(GuiGraphics context, String text, int x, int y, int color, boolean shadow) {
flushReleases();
Key key = new Key(color, text);
Key key = new Key(color, text, shadow);
Glyph glyph = GLYPHS.get(key);
if (glyph == null) {
glyph = createGlyph(text, color);
glyph = createGlyph(text, color, shadow);
if (glyph == null) return;
GLYPHS.put(key, glyph);
}
@@ -308,11 +334,11 @@ public final class FrosFont {
context.pose().popMatrix();
}
private static Glyph createGlyph(String text, int baseColor) {
private static Glyph createGlyph(String text, int baseColor, boolean shadow) {
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;
@@ -322,7 +348,7 @@ public final class FrosFont {
Graphics2D graphics = image.createGraphics();
graphics.setFont(awtFont);
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
drawSegments(graphics, text, baseColor);
drawSegments(graphics, text, baseColor, shadow);
graphics.dispose();
NativeImage nativeImage = new NativeImage(imageWidth, imageHeight, false);
@@ -344,8 +370,8 @@ public final class FrosFont {
return new Glyph(id, imageWidth, imageHeight);
}
private static void drawSegments(Graphics2D graphics, String text, int baseColor) {
int baseline = metrics.getAscent() + loadedScale;
private static void drawSegments(Graphics2D graphics, String text, int baseColor, boolean shadow) {
int baseline = metrics.getAscent() + Math.round(loadedScale);
int cursor = 1;
int current = baseColor;
int segmentStart = 0;
@@ -353,7 +379,10 @@ public final class FrosFont {
while (i < text.length()) {
if (text.charAt(i) == '\u00A7' && i + 1 < text.length()) {
cursor = flush(graphics, text, segmentStart, i, cursor, current, baseline);
// colored segments darken like vanilla's shadows do, instead
// of punching through at full brightness
cursor = flush(graphics, text, segmentStart, i, cursor,
shadow ? shadowColor(current) : current, baseline);
char code = Character.toLowerCase(text.charAt(i + 1));
i += 2;
@@ -381,7 +410,8 @@ public final class FrosFont {
}
i++;
}
flush(graphics, text, segmentStart, text.length(), cursor, current, baseline);
flush(graphics, text, segmentStart, text.length(), cursor,
shadow ? shadowColor(current) : current, baseline);
}
private static int flush(Graphics2D graphics, String text, int start, int end, int cursor, int color, int baseline) {