GUIScale only scales HUD modules; renamed to HUDScale
- ClickGui and the hud editor widget panel are unscaled again (raw mouse, scroll step 10) - FrosFont rasterizes per ui-scale state: hud rendering sets a context scale so its glyphs stay 1:1 device pixels, clickgui text keeps its own raster; the glyph cache is keyed by scale so switching between the two contexts never invalidates either - scale changes no longer clear the glyph cache, only font/size changes do
This commit is contained in:
@@ -4,7 +4,6 @@ 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;
|
||||
@@ -86,38 +85,27 @@ public class FrosGui extends Screen {
|
||||
|
||||
context.fill(0, 0, context.guiWidth(), context.guiHeight(), new Color(0, 0, 0, 120).hashCode());
|
||||
|
||||
// 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();
|
||||
this.widgets.forEach(components -> components.drawScreen(context, mouseX, mouseY, delta));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseClicked(MouseButtonEvent click, boolean doubled) {
|
||||
float scale = ClickGuiModule.scale();
|
||||
this.widgets.forEach(components -> components.mouseClicked((int) (click.x() / scale), (int) (click.y() / scale), click.button()));
|
||||
this.widgets.forEach(components -> components.mouseClicked((int) click.x(), (int) click.y(), click.button()));
|
||||
return super.mouseClicked(click, doubled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseReleased(MouseButtonEvent click) {
|
||||
float scale = ClickGuiModule.scale();
|
||||
this.widgets.forEach(components -> components.mouseReleased((int) (click.x() / scale), (int) (click.y() / scale), click.button()));
|
||||
this.widgets.forEach(components -> components.mouseReleased((int) click.x(), (int) click.y(), 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() - step));
|
||||
this.widgets.forEach(component -> component.setY(component.getY() - 10));
|
||||
} else if (verticalAmount > 0) {
|
||||
this.widgets.forEach(component -> component.setY(component.getY() + step));
|
||||
this.widgets.forEach(component -> component.setY(component.getY() + 10));
|
||||
}
|
||||
return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ 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;
|
||||
@@ -41,37 +40,27 @@ public class HudEditorScreen extends Screen {
|
||||
@Override
|
||||
public void render(GuiGraphics context, int mouseX, int mouseY, float delta) {
|
||||
anyHover = false;
|
||||
// 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();
|
||||
this.components.forEach(component -> component.drawScreen(context, mouseX, mouseY, delta));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseClicked(MouseButtonEvent click, boolean doubled) {
|
||||
float scale = ClickGuiModule.scale();
|
||||
this.components.forEach(component -> component.mouseClicked((int) (click.x() / scale), (int) (click.y() / scale), click.button()));
|
||||
this.components.forEach(component -> component.mouseClicked((int) click.x(), (int) click.y(), click.button()));
|
||||
return super.mouseClicked(click, doubled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseReleased(MouseButtonEvent click) {
|
||||
float scale = ClickGuiModule.scale();
|
||||
this.components.forEach(component -> component.mouseReleased((int) (click.x() / scale), (int) (click.y() / scale), click.button()));
|
||||
this.components.forEach(component -> component.mouseReleased((int) click.x(), (int) click.y(), 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() - step));
|
||||
this.components.forEach(component -> component.setY(component.getY() - 10));
|
||||
} else if (verticalAmount > 0) {
|
||||
this.components.forEach(component -> component.setY(component.getY() + step));
|
||||
this.components.forEach(component -> component.setY(component.getY() + 10));
|
||||
}
|
||||
return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ public class ClickGuiModule extends Module {
|
||||
|
||||
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<Float> hudScale = num("HUDScale", 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,8 +75,8 @@ public class ClickGuiModule extends Module {
|
||||
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());
|
||||
/** scale factor applied to the client's own hud modules */
|
||||
public static float hudScale() {
|
||||
return INSTANCE == null ? 1f : Math.max(0.5f, INSTANCE.hudScale.getValue());
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ public abstract class HudModule extends Module {
|
||||
}
|
||||
|
||||
public float getY() {
|
||||
float scaledHeight = getHeight() * ClickGuiModule.scale();
|
||||
float scaledHeight = getHeight() * ClickGuiModule.hudScale();
|
||||
float heightWithChat = mc.getWindow().getGuiScaledHeight() - 14;
|
||||
float baseY = mc.getWindow().getGuiScaledHeight() * pos.getValue().y();
|
||||
float combined = baseY + scaledHeight;
|
||||
@@ -50,7 +50,9 @@ public abstract class HudModule extends Module {
|
||||
|
||||
@Subscribe
|
||||
public void onRender2DHud(Render2DEvent e) {
|
||||
float scale = ClickGuiModule.scale();
|
||||
float scale = ClickGuiModule.hudScale();
|
||||
// glyphs rasterize for the hud's transform so they stay crisp
|
||||
FrosFont.setContextScale(scale);
|
||||
if (scale == 1f) {
|
||||
render(e);
|
||||
return;
|
||||
@@ -66,6 +68,8 @@ public abstract class HudModule extends Module {
|
||||
render(e);
|
||||
} finally {
|
||||
e.getContext().pose().popMatrix();
|
||||
// everything else in the frame draws unscaled
|
||||
FrosFont.setContextScale(1f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +103,7 @@ public abstract class HudModule extends Module {
|
||||
}
|
||||
|
||||
if (dragging) {
|
||||
float scale = ClickGuiModule.scale();
|
||||
float scale = ClickGuiModule.hudScale();
|
||||
float maxX = mc.getWindow().getGuiScaledWidth() - width * scale;
|
||||
float maxY = mc.getWindow().getGuiScaledHeight() - height * scale;
|
||||
float rawX = Math.min(Math.max(getMouseX() - dragX, 0), maxX);
|
||||
@@ -123,7 +127,7 @@ public abstract class HudModule extends Module {
|
||||
if (shouldDrawDescription) {
|
||||
int textWidth = FrosFont.width(getName());
|
||||
int textHeight = FrosFont.lineHeight();
|
||||
float scale = ClickGuiModule.scale();
|
||||
float scale = ClickGuiModule.hudScale();
|
||||
float textX = x + width + 5;
|
||||
// the label is drawn inside the element's scale transform
|
||||
if (x + (textX + textWidth - x) * scale > mc.getWindow().getGuiScaledWidth()) {
|
||||
@@ -159,7 +163,7 @@ public abstract class HudModule extends Module {
|
||||
float y = getY();
|
||||
int mouseX = getMouseX();
|
||||
int mouseY = getMouseY();
|
||||
float scale = ClickGuiModule.scale();
|
||||
float scale = ClickGuiModule.hudScale();
|
||||
|
||||
return mouseX >= x - 1 && mouseX <= x + width * scale + 1 &&
|
||||
mouseY >= y - 1 && mouseY <= y + height * scale + 1;
|
||||
|
||||
@@ -18,6 +18,7 @@ import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -67,12 +68,24 @@ public final class FrosFont {
|
||||
private static final List<Identifier> PENDING_RELEASE = new ArrayList<>();
|
||||
private static long lastQueuedAt;
|
||||
|
||||
private record Key(int color, String text, boolean shadow) {
|
||||
private record Key(int color, String text, boolean shadow, float scale) {
|
||||
}
|
||||
|
||||
private record Glyph(Identifier id, int width, int height) {
|
||||
}
|
||||
|
||||
/** a raster font for one ui scale; the clickgui and the scaled hud each get their own */
|
||||
private record FontState(java.awt.Font font, FontMetrics metrics, float visualCenterRaster) {
|
||||
}
|
||||
|
||||
// the family picked in the settings, independent of any scale
|
||||
private static java.awt.Font baseFont;
|
||||
private static final Map<Float, FontState> STATES = new HashMap<>();
|
||||
// outer transform applied by the hud's render pose; glyphs rasterize for
|
||||
// it so they stay 1:1 device pixels at any hud scale
|
||||
private static float contextScale = 1f;
|
||||
|
||||
// current state pointers, re-pointed by sync() for the active context
|
||||
private static java.awt.Font awtFont;
|
||||
private static FontMetrics metrics;
|
||||
private static String loadedName;
|
||||
@@ -82,6 +95,16 @@ public final class FrosFont {
|
||||
private static float visualCenterRaster = 4f;
|
||||
private static int glyphCounter;
|
||||
|
||||
/**
|
||||
* Sets the ui transform currently applied to drawing outside this class.
|
||||
* hud modules scale their pose by HUDScale, so glyphs rasterize larger for
|
||||
* them; the unscaled clickgui keeps its own, so switching between the two
|
||||
* never invalidates the other's glyph cache.
|
||||
*/
|
||||
public static void setContextScale(float scale) {
|
||||
contextScale = Math.max(0.5f, scale);
|
||||
}
|
||||
|
||||
private FrosFont() {
|
||||
}
|
||||
|
||||
@@ -111,9 +134,9 @@ public final class FrosFont {
|
||||
|
||||
private static float currentScale() {
|
||||
try {
|
||||
// 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()));
|
||||
// one raster pixel per screen pixel, following mc's gui scale and
|
||||
// the hud's own scale (contextScale)
|
||||
return Math.max(0.5f, (float) (mc.getWindow().getGuiScale() * contextScale));
|
||||
} catch (Exception e) {
|
||||
return 1f;
|
||||
}
|
||||
@@ -151,56 +174,85 @@ public final class FrosFont {
|
||||
String name = gui == null ? "" : gui.font.getValue();
|
||||
int size = gui == null ? 10 : gui.fontSize.getValue();
|
||||
if (name == null) name = "";
|
||||
float scale = currentScale();
|
||||
|
||||
if (!Objects.equals(name, loadedName) || size != loadedSize || scale != loadedScale) {
|
||||
reset(name, size, scale);
|
||||
// the settings determine the family and its unit size; a scale change
|
||||
// only adds another raster state and must not clear the glyph cache
|
||||
if (!Objects.equals(name, loadedName) || size != loadedSize) {
|
||||
reset(name, size);
|
||||
}
|
||||
if (baseFont == null) {
|
||||
awtFont = null;
|
||||
metrics = null;
|
||||
return;
|
||||
}
|
||||
|
||||
float scale = currentScale();
|
||||
FontState state = STATES.get(scale);
|
||||
if (state == null) {
|
||||
state = createState(scale);
|
||||
if (state == null) {
|
||||
baseFont = null;
|
||||
awtFont = null;
|
||||
metrics = null;
|
||||
return;
|
||||
}
|
||||
STATES.put(scale, state);
|
||||
}
|
||||
|
||||
awtFont = state.font();
|
||||
metrics = state.metrics();
|
||||
visualCenterRaster = state.visualCenterRaster();
|
||||
loadedScale = scale;
|
||||
}
|
||||
|
||||
private static void reset(String name, float size, float scale) {
|
||||
private static void reset(String name, float size) {
|
||||
loadedName = name;
|
||||
loadedSize = size;
|
||||
loadedScale = scale;
|
||||
baseFont = null;
|
||||
awtFont = null;
|
||||
metrics = null;
|
||||
STATES.clear();
|
||||
clearGlyphs();
|
||||
|
||||
if (name.isBlank()) return;
|
||||
try {
|
||||
java.awt.Font installed = installedFont(name);
|
||||
if (installed != null) {
|
||||
applyFont(installed, size * scale, "Using system font '" + name + "'", true);
|
||||
baseFont = installed;
|
||||
LOGGER.info("Using system font '{}' ({}pt)", name, size);
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.warn("Font '{}' not found", name);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to load font '{}'", name, e);
|
||||
awtFont = null;
|
||||
metrics = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyFont(java.awt.Font font, float size, String source, boolean verbose) {
|
||||
awtFont = font.deriveFont(java.awt.Font.PLAIN, size);
|
||||
BufferedImage probe = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
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();
|
||||
private static FontState createState(float scale) {
|
||||
try {
|
||||
java.awt.Font font = baseFont.deriveFont(java.awt.Font.PLAIN, loadedSize * scale);
|
||||
BufferedImage probe = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D graphics = probe.createGraphics();
|
||||
graphics.setFont(font);
|
||||
FontMetrics fontMetrics = 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 = font
|
||||
.createGlyphVector(graphics.getFontRenderContext(), "A")
|
||||
.getVisualBounds();
|
||||
float capHeight = (float) capBounds.getHeight();
|
||||
if (capHeight <= 0f || capHeight > fontMetrics.getAscent()) {
|
||||
capHeight = fontMetrics.getAscent();
|
||||
}
|
||||
float visualCenter = fontMetrics.getAscent() - capHeight / 2f;
|
||||
graphics.dispose();
|
||||
return new FontState(font, fontMetrics, visualCenter);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to create font at scale {}", scale, e);
|
||||
return null;
|
||||
}
|
||||
visualCenterRaster = metrics.getAscent() - capHeight / 2f;
|
||||
graphics.dispose();
|
||||
if (verbose) LOGGER.info("{} ({}pt)", source, size);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -259,7 +311,7 @@ public final class FrosFont {
|
||||
|
||||
private static void drawGlyphs(GuiGraphics context, String text, int x, int y, int color, boolean shadow) {
|
||||
flushReleases();
|
||||
Key key = new Key(color, text, shadow);
|
||||
Key key = new Key(color, text, shadow, loadedScale);
|
||||
Glyph glyph = GLYPHS.get(key);
|
||||
if (glyph == null) {
|
||||
glyph = createGlyph(text, color, shadow);
|
||||
|
||||
Reference in New Issue
Block a user