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
This commit is contained in:
froslanz
2026-10-22 12:57:02 -04:00
parent 3568996138
commit 9b6a55d66c
@@ -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;
@@ -93,12 +100,15 @@ 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 * scale);
float finalY = Math.min(Math.max(getMouseY() - dragY, 0),
mc.getWindow().getGuiScaledHeight() - height * 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 {
@@ -155,6 +165,43 @@ public abstract class HudModule extends Module {
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() {
return width;
}