Rework the HUD editor
HUDs are drawn as they are in game, background included, through the new HudModPreview.draw, and the world behind the editor isn't blurred. Each has an outline that turns the accent color with the mod's name while hovered or dragged, and a visible resize handle that's easier to grab. Scale stays within its range and HUDs stay on screen, and a hint says how to use the editor. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -15,13 +15,21 @@ public class HudModPreview extends Element {
|
||||
|
||||
@Override
|
||||
public void render(RenderScope scope, ElementContext ctx) {
|
||||
ModLayout layout = feature.getDimensions();
|
||||
draw(scope, feature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a HUD mod with sample content and the background it has in game,
|
||||
* at the origin and its layout's unscaled size.
|
||||
*/
|
||||
public static void draw(RenderScope scope, HudMod mod) {
|
||||
ModLayout layout = mod.getDimensions();
|
||||
|
||||
if (layout.renderBackground) {
|
||||
scope.drawRoundedRectangle(0, 0, layout.width, layout.height,
|
||||
layout.radius.value, layout.bgColor.value);
|
||||
}
|
||||
|
||||
feature.renderDummy(scope);
|
||||
mod.renderDummy(scope);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +1,71 @@
|
||||
package org.saturnclient.ui.screens;
|
||||
|
||||
import org.saturnclient.common.ref.render.MatrixStackRef;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.saturnclient.config.ConfigManager;
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.mod.HudMod;
|
||||
import org.saturnclient.mod.Mod;
|
||||
import org.saturnclient.mod.ModLayout;
|
||||
import org.saturnclient.mod.ModManager;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.SaturnScreen;
|
||||
import org.saturnclient.ui.components.HudModPreview;
|
||||
import org.saturnclient.ui.resources.Fonts;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Moves and resizes the enabled HUD mods, drawn as they are in game. Drag one
|
||||
* to move it, or drag its corner handle to resize it.
|
||||
*
|
||||
* Unlike other Saturn screens this works in GUI pixels, like the HUD itself:
|
||||
* it draws without the half scale and takes the mouse unscaled.
|
||||
*/
|
||||
public class HudEditor extends SaturnScreen {
|
||||
private static final String HINT = "Drag to move · Drag the corner to resize · Esc when done";
|
||||
|
||||
/** Size of the resize handle, and how far around it still grabs it */
|
||||
private static final int HANDLE = 4;
|
||||
private static final int HANDLE_REACH = 4;
|
||||
private static final float LABEL_SCALE = 0.4f;
|
||||
private static final float HINT_SCALE = 0.45f;
|
||||
|
||||
private final List<HudMod> hudMods = new ArrayList<>();
|
||||
|
||||
private HudMod draggingMod = null;
|
||||
private boolean resizing = false;
|
||||
private int offsetX, offsetY;
|
||||
private static final int RESIZE_MARGIN = 3;
|
||||
|
||||
public HudEditor() {
|
||||
super("HUD Editor");
|
||||
for (Mod m : ModManager.ENABLED_MODS) {
|
||||
if (m.isEnabled() && m instanceof HudMod) {
|
||||
hudMods.add((HudMod) m);
|
||||
if (m.isEnabled() && m instanceof HudMod hudMod) {
|
||||
hudMods.add(hudMod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ui() {
|
||||
// Placing HUDs needs a clear view of the game.
|
||||
blurBackground = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseClicked(double mouseX, double mouseY, int button) {
|
||||
if (button == 0) {
|
||||
for (int m = hudMods.size() - 1; m >= 0; m--) {
|
||||
HudMod mod = hudMods.get(m);
|
||||
HudMod mod = getModAt(mouseX, mouseY);
|
||||
|
||||
if (mod != null) {
|
||||
ModLayout dim = mod.getDimensions();
|
||||
|
||||
// Check if clicking the corner for resizing
|
||||
if (isCorner(mouseX, mouseY, dim)) {
|
||||
draggingMod = mod;
|
||||
resizing = true;
|
||||
offsetX = (int) (mouseX - (dim.x.value + dim.width * dim.scale.value));
|
||||
offsetY = (int) (mouseY - (dim.y.value + dim.height * dim.scale.value));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if clicking inside the mod
|
||||
if (isInside(mouseX, mouseY, dim)) {
|
||||
draggingMod = mod;
|
||||
resizing = false;
|
||||
offsetX = (int) (mouseX - dim.x.value);
|
||||
offsetY = (int) (mouseY - dim.y.value);
|
||||
return true;
|
||||
}
|
||||
draggingMod = mod;
|
||||
resizing = isOnHandle(mouseX, mouseY, dim);
|
||||
offsetX = (int) (mouseX - (resizing ? right(dim) : dim.x.value));
|
||||
offsetY = (int) (mouseY - (resizing ? bottom(dim) : dim.y.value));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return super.mouseClicked(mouseX, mouseY, button);
|
||||
}
|
||||
|
||||
@@ -72,57 +84,121 @@ public class HudEditor extends SaturnScreen {
|
||||
ModLayout dim = draggingMod.getDimensions();
|
||||
|
||||
if (resizing) {
|
||||
// Scale based on cursor movement
|
||||
float newScale = Math.max(0.5f, ((float) (mouseX - dim.x.value) / dim.width));
|
||||
dim.scale.value = newScale;
|
||||
// The handle follows the cursor: the scale is the new width over the unscaled width.
|
||||
float scale = (float) (mouseX - offsetX - dim.x.value) / dim.width;
|
||||
float min = dim.scale.min != null ? dim.scale.min : 0.5f;
|
||||
float max = dim.scale.max != null ? dim.scale.max : 4f;
|
||||
dim.scale.value = Math.max(min, Math.min(max, scale));
|
||||
} else {
|
||||
// Move the mod
|
||||
dim.x.value = (int) mouseX - offsetX;
|
||||
dim.y.value = (int) mouseY - offsetY;
|
||||
}
|
||||
|
||||
keepOnScreen(dim);
|
||||
}
|
||||
|
||||
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ui() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, int mouseX, int mouseY, float delta, long elapsed) {
|
||||
// The screen hands over the mouse in Saturn units, twice GUI pixels.
|
||||
int guiMouseX = mouseX / 2;
|
||||
int guiMouseY = mouseY / 2;
|
||||
HudMod hovered = draggingMod != null ? draggingMod : getModAt(guiMouseX, guiMouseY);
|
||||
|
||||
for (HudMod mod : hudMods) {
|
||||
ModLayout dim = mod.getDimensions();
|
||||
MatrixStackRef matrices = renderScope.getMatrixStack();
|
||||
matrices.push();
|
||||
boolean active = mod == hovered;
|
||||
|
||||
matrices.translate(dim.x.value, dim.y.value);
|
||||
matrices.scale(dim.scale.value, dim.scale.value);
|
||||
renderScope.getMatrixStack().push();
|
||||
renderScope.getMatrixStack().translate(dim.x.value, dim.y.value);
|
||||
renderScope.getMatrixStack().scale(dim.scale.value, dim.scale.value);
|
||||
HudModPreview.draw(renderScope, mod);
|
||||
renderScope.getMatrixStack().pop();
|
||||
|
||||
// Render the mod
|
||||
mod.renderDummy(renderScope);
|
||||
drawFrame(renderScope, mod, dim, active);
|
||||
}
|
||||
|
||||
matrices.pop();
|
||||
drawHint(renderScope);
|
||||
}
|
||||
|
||||
// Draw border around mod
|
||||
int borderX = dim.x.value;
|
||||
int borderY = dim.y.value;
|
||||
int borderW = (int) (dim.width * dim.scale.value);
|
||||
int borderH = (int) (dim.height * dim.scale.value);
|
||||
/**
|
||||
* The outline around a HUD, its resize handle, and its name while it's
|
||||
* hovered or dragged.
|
||||
*/
|
||||
private void drawFrame(RenderScope renderScope, HudMod mod, ModLayout dim, boolean active) {
|
||||
int x = dim.x.value;
|
||||
int y = dim.y.value;
|
||||
int w = right(dim) - x;
|
||||
int h = bottom(dim) - y;
|
||||
int color = active ? Theme.ACCENT.value : Theme.withAlpha(0.45f, Theme.FOREGROUND.value);
|
||||
|
||||
renderScope.drawBorder(borderX, borderY, borderW, borderH, 0xFFFFFFFF); // White border
|
||||
if (active) {
|
||||
renderScope.drawRectangle(x, y, w, h, Theme.withAlpha(0.08f, Theme.ACCENT.value));
|
||||
}
|
||||
|
||||
renderScope.drawBorder(x - 1, y - 1, w + 2, h + 2, color);
|
||||
renderScope.drawRectangle(x + w - HANDLE / 2, y + h - HANDLE / 2, HANDLE, HANDLE, color);
|
||||
|
||||
if (active && mod instanceof Mod named) {
|
||||
int labelHeight = (int) (Fonts.getHeight() * LABEL_SCALE);
|
||||
int labelY = y - labelHeight - 4 < 0 ? y + h + 4 : y - labelHeight - 4;
|
||||
renderScope.drawText(LABEL_SCALE, named.getName(), x, labelY, Theme.FONT.value, Theme.FOREGROUND.value);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInside(double mouseX, double mouseY, ModLayout dim) {
|
||||
return mouseX >= dim.x.value && mouseX <= dim.x.value + (dim.width * dim.scale.value) &&
|
||||
mouseY >= dim.y.value && mouseY <= dim.y.value + (dim.height * dim.scale.value);
|
||||
private void drawHint(RenderScope renderScope) {
|
||||
int guiWidth = width / 2;
|
||||
int textWidth = (int) (Fonts.getWidth(HINT, Theme.getBodyFont()) * HINT_SCALE);
|
||||
int textHeight = (int) (Fonts.getHeight() * HINT_SCALE);
|
||||
int pillWidth = textWidth + 20;
|
||||
int pillHeight = textHeight + 10;
|
||||
int x = (guiWidth - pillWidth) / 2;
|
||||
int y = 8;
|
||||
|
||||
renderScope.drawRoundedRectangle(x, y, pillWidth, pillHeight, pillHeight / 2, 1, Theme.getPanelBg(),
|
||||
Theme.getBorder());
|
||||
renderScope.drawText(HINT_SCALE, HINT, x + 10, y + 5, Theme.getBodyFont(), Theme.PRIMARY_FG.value);
|
||||
}
|
||||
|
||||
private boolean isCorner(double mouseX, double mouseY, ModLayout dim) {
|
||||
float rightX = dim.x.value + dim.width * dim.scale.value;
|
||||
float bottomY = dim.y.value + dim.height * dim.scale.value;
|
||||
/**
|
||||
* The HUD under the mouse, preferring the one drawn on top, or null.
|
||||
*/
|
||||
private HudMod getModAt(double mouseX, double mouseY) {
|
||||
for (int i = hudMods.size() - 1; i >= 0; i--) {
|
||||
ModLayout dim = hudMods.get(i).getDimensions();
|
||||
if (isOnHandle(mouseX, mouseY, dim) || isInside(mouseX, mouseY, dim)) {
|
||||
return hudMods.get(i);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return Math.abs(mouseX - rightX) < RESIZE_MARGIN && Math.abs(mouseY - bottomY) < RESIZE_MARGIN;
|
||||
private void keepOnScreen(ModLayout dim) {
|
||||
int guiWidth = width / 2;
|
||||
int guiHeight = height / 2;
|
||||
int w = right(dim) - dim.x.value;
|
||||
int h = bottom(dim) - dim.y.value;
|
||||
|
||||
dim.x.value = Math.max(0, Math.min(dim.x.value, guiWidth - w));
|
||||
dim.y.value = Math.max(0, Math.min(dim.y.value, guiHeight - h));
|
||||
}
|
||||
|
||||
private static int right(ModLayout dim) {
|
||||
return dim.x.value + (int) (dim.width * dim.scale.value);
|
||||
}
|
||||
|
||||
private static int bottom(ModLayout dim) {
|
||||
return dim.y.value + (int) (dim.height * dim.scale.value);
|
||||
}
|
||||
|
||||
private static boolean isInside(double mouseX, double mouseY, ModLayout dim) {
|
||||
return mouseX >= dim.x.value && mouseX <= right(dim) && mouseY >= dim.y.value && mouseY <= bottom(dim);
|
||||
}
|
||||
|
||||
private static boolean isOnHandle(double mouseX, double mouseY, ModLayout dim) {
|
||||
return Math.abs(mouseX - right(dim)) <= HANDLE_REACH && Math.abs(mouseY - bottom(dim)) <= HANDLE_REACH;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user