Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
## Unreleased changes
- Added a Scale setting to HUDs that stays in sync with the HUD editor's corner handles
- Dropped compatibility with Hypixel SkyBlock mods (Apec, Firmament, Odin, Skyblocker, SkyHanni and other MoulConfig-based mods) on Minecraft versions below 26.1, which can no longer be used to play SkyBlock
3 changes: 3 additions & 0 deletions modules/hud/api/hud.api
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ public abstract class org/polyfrost/oneconfig/api/hud/v1/Hud : org/polyfrost/one
public final fun getFullShadow ()Z
public final fun getGrowthAnchor ()Lorg/polyfrost/oneconfig/api/hud/v1/HudAnchor;
public fun getHidden ()Z
public final fun getHudScale ()F
public final fun getHudScaleRange ()Lkotlin/ranges/ClosedFloatingPointRange;
public final fun getKeepBgWhenHidden ()Z
public fun getLocked ()Z
public final fun getMergeBackground ()Z
Expand Down Expand Up @@ -158,6 +160,7 @@ public abstract class org/polyfrost/oneconfig/api/hud/v1/Hud : org/polyfrost/one
public final fun setGrowthAnchor (Lorg/polyfrost/oneconfig/api/hud/v1/HudAnchor;)V
public final fun setGrowthAnchorKeepingPosition (Lorg/polyfrost/oneconfig/api/hud/v1/HudAnchor;)V
public fun setHidden (Z)V
public final fun setHudScale (F)V
public final fun setKeepBgWhenHidden (Z)V
public final fun setLayoutRefH (F)V
public final fun setLayoutRefW (F)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ private const val MIN_VISIBLE = 12f

internal const val POS_SCHEMA = 1

private const val MIN_TEXT_SCALE = 6f / 14f
private const val MAX_TEXT_SCALE = 64f / 14f
private const val MIN_LEGACY_SCALE = 0.25f
private const val MAX_LEGACY_SCALE = 4f

// every open getter the cached frame geometry or the background merge key reads
// a subclass overriding one of these is read live, so a new open geometry property must be added here
// or an override of it would be served a stale cached value
Expand Down Expand Up @@ -980,6 +985,35 @@ abstract class Hud(id: String, title: String, val category: Category) : Cloneabl
private var _customScale: MutableState<Float> = mutableStateOf(1f)
open var customScale: Float get() = _customScale.value; set(v) { _customScale.put(v) }

/** The values [hudScale] can take */
@get:ApiStatus.Experimental
val hudScaleRange: ClosedFloatingPointRange<Float>
get() = if (this is LegacyHudMarker) MIN_LEGACY_SCALE..MAX_LEGACY_SCALE else MIN_TEXT_SCALE..MAX_TEXT_SCALE

/**
* The size of this HUD as set by the editor's corner handles and the Scale setting
*
* That is [textScale], or [customScale] for a legacy HUD which draws its own content
*
* Setting it keeps a static size in proportion, which setting [textScale] directly does not
*/
@get:ApiStatus.Experimental
var hudScale: Float
get() = if (this is LegacyHudMarker) customScale else textScale
set(v) {
val next = v.coerceIn(hudScaleRange)
if (this is LegacyHudMarker) {
customScale = next
return
}
val previous = textScale
textScale = next
if (staticWidth && previous > 0f && staticW > 0f && staticH > 0f) {
staticW *= next / previous
staticH *= next / previous
}
}

val effectiveScale: Float
get() {
if (useGuiScale) return customScale
Expand Down Expand Up @@ -1424,7 +1458,10 @@ abstract class Hud(id: String, title: String, val category: Category) : Cloneabl
}
tree["font"] = ktProperty(out::font).apply { addDisplayCondition(hideFromConfigUi) }
tree["caseType"] = ktProperty(out::caseType).apply { addDisplayCondition(hideFromConfigUi) }
tree["textScale"] = ktProperty(out::textScale).apply { addDisplayCondition(hideFromConfigUi) }
tree["textScale"] = ktProperty(out::textScale).apply {
description = "The size of this HUD. The corner handles in the HUD editor change the same value."
addDisplayCondition(hideFromConfigUi)
}
tree["textBold"] = ktProperty(out::textBold).apply { addDisplayCondition(hideFromConfigUi) }
tree["textItalic"] = ktProperty(out::textItalic).apply { addDisplayCondition(hideFromConfigUi) }
tree["textUnderline"] = ktProperty(out::textUnderline).apply { addDisplayCondition(hideFromConfigUi) }
Expand All @@ -1435,7 +1472,10 @@ abstract class Hud(id: String, title: String, val category: Category) : Cloneabl
addDisplayCondition(hideFromConfigUi)
}
if (out.persistOwnState) {
tree["customScale"] = ktProperty(out::customScale).apply { addDisplayCondition(hideFromConfigUi) }
tree["customScale"] = ktProperty(out::customScale).apply {
description = "Multiplies the whole HUD on top of its Scale, including its padding and background."
addDisplayCondition(hideFromConfigUi)
}
}
tree["showBackground"] = ktProperty(out::showBackground).apply { addDisplayCondition(hideFromConfigUi) }
tree["bgColor"] = ktProperty(out::bgColor).apply { addDisplayCondition(hideFromConfigUi) }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.polyfrost.oneconfig.api.hud.v1;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class HudScaleTest {
static class PlainHud extends TextHud {
PlainHud() { super("scale-plain", "Plain", Hud.Category.getINFO(), "", ""); }
@Override public String getText() { return "plain"; }
}

static class LegacyHud extends PlainHud implements LegacyHudMarker {
@Override public boolean getSupportsScale() { return true; }
}

@Test
void scaleIsTheValueTheCornerHandlesDrag() {
Hud hud = new PlainHud();
hud.setHudScale(2f);
assertEquals(2f, hud.getTextScale(), 0.001f);
assertEquals(2f, hud.getHudScale(), 0.001f);
assertEquals(1f, hud.getCustomScale(), 0.001f, "the scale stays separate from Custom Scale");
}

@Test
void aStaticBoxStaysInProportion() {
Hud hud = new PlainHud();
hud.setStaticWidth(true);
hud.setStaticW(100f);
hud.setStaticH(20f);
hud.setHudScale(1.5f);
assertEquals(150f, hud.getStaticW(), 0.001f);
assertEquals(30f, hud.getStaticH(), 0.001f);
}

@Test
void scaleStaysInRange() {
Hud hud = new PlainHud();
hud.setHudScale(100f);
assertEquals(hud.getHudScaleRange().getEndInclusive(), hud.getHudScale(), 0.001f);
hud.setHudScale(-1f);
assertEquals(hud.getHudScaleRange().getStart(), hud.getHudScale(), 0.001f);
}

@Test
void legacyHudsScaleThroughCustomScale() {
Hud hud = new LegacyHud();
hud.setHudScale(2f);
assertEquals(2f, hud.getCustomScale(), 0.001f);
assertEquals(1f, hud.getTextScale(), 0.001f);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -698,14 +698,28 @@ private fun hitTestResizeHandle(hud: Hud, screenX: Float, screenY: Float, mcToSc
}
}

/**
* How far a resize from [corner] has scaled a box that started as [start] with the cursor at
* ([mouseX], [mouseY])
*
* The cursor is projected onto the diagonal from the fixed corner to the dragged one because a
* per-axis factor turns small moves on the short axis into huge jumps
*/
private fun cornerScaleFactor(corner: ResizeCorner, start: HudBounds, mouseX: Float, mouseY: Float): Float {
val anchorX = if (corner.isLeft) start.x + start.width else start.x
val anchorY = if (corner.isTop) start.y + start.height else start.y
val cornerVecX = if (corner.isLeft) -start.width else start.width
val cornerVecY = if (corner.isTop) -start.height else start.height
val diagLenSq = cornerVecX * cornerVecX + cornerVecY * cornerVecY
if (diagLenSq <= 0f) return 1f
return ((mouseX - anchorX) * cornerVecX + (mouseY - anchorY) * cornerVecY) / diagLenSq
}

private fun resizeHud(
hud: Hud,
corner: ResizeCorner,
startBounds: HudBounds,
startTextScale: Float,
startScale: Float,
startStaticW: Float,
startStaticH: Float,
mouseX: Float,
mouseY: Float,
freeResize: Boolean,
Expand All @@ -720,22 +734,7 @@ private fun resizeHud(
return
}

if (hud is LegacyHud) {
val cornerVecX = if (corner.isLeft) -startBounds.width else startBounds.width
val cornerVecY = if (corner.isTop) -startBounds.height else startBounds.height
val mouseVecX = mouseX - anchorX
val mouseVecY = mouseY - anchorY
val diagLenSq = cornerVecX * cornerVecX + cornerVecY * cornerVecY
val rawFactor = if (diagLenSq > 0f) {
(mouseVecX * cornerVecX + mouseVecY * cornerVecY) / diagLenSq
} else 1f
val baseScale = startScale.coerceAtLeast(0.001f)
val newScale = (baseScale * rawFactor).coerceIn(0.25f, 4f)
hud.customScale = newScale
return
}

if (freeResize) {
if (freeResize && hud !is LegacyHud) {
val effectiveScale = hud.effectiveScale.coerceAtLeast(0.001f)
val (minStaticW, minStaticH) = hud.minimumSize()
val newWidth = targetWidth.coerceAtLeast(minStaticW * effectiveScale)
Expand All @@ -747,26 +746,7 @@ private fun resizeHud(
return
}

// scale proportionally by projecting the mouse onto the diagonal from the anchor to the dragged
// corner because a per-axis max-deviation factor turns small moves on the short axis into huge jumps
val cornerVecX = if (corner.isLeft) -startBounds.width else startBounds.width
val cornerVecY = if (corner.isTop) -startBounds.height else startBounds.height
val mouseVecX = mouseX - anchorX
val mouseVecY = mouseY - anchorY
val diagLenSq = cornerVecX * cornerVecX + cornerVecY * cornerVecY
val rawFactor = if (diagLenSq > 0f) {
(mouseVecX * cornerVecX + mouseVecY * cornerVecY) / diagLenSq
} else 1f
val minTextScale = 6f / 14f
val maxTextScale = 64f / 14f
val newTextScale = (startTextScale * rawFactor).coerceIn(minTextScale, maxTextScale)
val factor = if (startTextScale > 0f) newTextScale / startTextScale else 1f

hud.textScale = newTextScale
if (hud.staticWidth && startStaticW > 0f && startStaticH > 0f) {
hud.staticW = startStaticW * factor
hud.staticH = startStaticH * factor
}
hud.hudScale = startScale * cornerScaleFactor(corner, startBounds, mouseX, mouseY)
}

private fun DrawScope.drawSelectedHudBounds(
Expand Down Expand Up @@ -1081,10 +1061,7 @@ fun HudDesignStudio(visible: Boolean = true) {
var resizedHud by remember { mutableStateOf<Hud?>(null) }
var resizeCorner by remember { mutableStateOf<ResizeCorner?>(null) }
var resizeStartBounds by remember { mutableStateOf<HudBounds?>(null) }
var resizeStartTextScale by remember { mutableStateOf(1f) }
var resizeStartScale by remember { mutableStateOf(1f) }
var resizeStartStaticW by remember { mutableStateOf(0f) }
var resizeStartStaticH by remember { mutableStateOf(0f) }
var libraryVisible by remember { mutableStateOf(true) }
var searchText by remember { mutableStateOf("") }
var searchFocusTarget by remember { mutableStateOf<StudioSearch?>(null) }
Expand Down Expand Up @@ -1603,10 +1580,7 @@ fun HudDesignStudio(visible: Boolean = true) {
resizedHud = selected
resizeCorner = handle
resizeStartBounds = bounds
resizeStartTextScale = selected.textScale
resizeStartScale = selected.customScale
resizeStartStaticW = selected.staticW
resizeStartStaticH = selected.staticH
resizeStartScale = selected.hudScale
hoveredHud = selected
closeLibrary()
pasteMenuOffset = null
Expand Down Expand Up @@ -1728,10 +1702,7 @@ fun HudDesignStudio(visible: Boolean = true) {
hud = hit,
corner = corner,
startBounds = bounds,
startTextScale = resizeStartTextScale,
startScale = resizeStartScale,
startStaticW = resizeStartStaticW,
startStaticH = resizeStartStaticH,
mouseX = pos.x * s,
mouseY = pos.y * s,
freeResize = event.keyboardModifiers.isShiftPressed,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ fun Designer(hud: Hud?, query: String) {
var padTop by remember { mutableStateOf(hud.padTop) }
var padBottom by remember { mutableStateOf(hud.padBottom) }
var font by remember { mutableStateOf(hud.font) }
var textScale by remember { mutableStateOf(hud.textScale) }
// read straight from the HUD so dragging a corner handle updates the panel while it is open
val textScale = hud.textScale
var textBold by remember { mutableStateOf(hud.textBold) }
var textItalic by remember { mutableStateOf(hud.textItalic) }
var textUnderline by remember { mutableStateOf(hud.textUnderline) }
Expand Down Expand Up @@ -295,7 +296,7 @@ fun Designer(hud: Hud?, query: String) {
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
Text("Font size", color = LocalTheme.current.textColor, fontSize = 14.sp)
NumberSpinner(
textScale * 14f, { Snapshot.withMutableSnapshot { val s = it / 14f; textScale = s; hud.textScale = s } },
textScale * 14f, { Snapshot.withMutableSnapshot { hud.hudScale = it / 14f } },
6f, 64f, 1f, width = 112.dp, unit = "px", icon = "align"
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import org.polyfrost.oneconfig.api.hud.v1.Hud
import org.polyfrost.oneconfig.api.hud.v1.HudManager
import org.polyfrost.oneconfig.api.hud.v1.HudResize
import org.polyfrost.oneconfig.api.hud.v1.LegacyHudMarker
import org.polyfrost.oneconfig.internal.ui.components.Icon
import org.polyfrost.oneconfig.internal.ui.components.Text
Expand All @@ -46,6 +47,25 @@ import org.polyfrost.oneconfig.internal.ui.themes.LocalTheme

private val DeleteButtonColor = Color(0xFFE5484D)

/** [Hud.hudScale], read straight from the HUD so dragging a corner handle updates it while the panel is open */
@Composable
private fun HudScaleSpinner(hud: Hud, optionId: String) {
val range = hud.hudScaleRange
HudSettingTarget(hud, optionId) {
NumberSpinner(
hud.hudScale,
{ Snapshot.withMutableSnapshot { hud.hudScale = it } },
range.start,
range.endInclusive,
0.25f,
width = 176.dp,
format = { "%.2f".format(it) },
unit = "x",
label = "Scale",
)
}
}

@Composable
fun Settings(hud: Hud?, query: String, onDeleted: () -> Unit) {
if (hud == null) return
Expand Down Expand Up @@ -106,21 +126,15 @@ fun Settings(hud: Hud?, query: String, onDeleted: () -> Unit) {
if (hud is LegacyHudMarker) {
if (hud.supportsScale) {
Section("Scale", query) {
HudSettingTarget(hud, "customScale") {
NumberSpinner(
customScale,
{ Snapshot.withMutableSnapshot { customScale = it; hud.customScale = it } },
0.25f,
4f,
0.25f,
width = 176.dp,
unit = "x",
label = "Scale",
)
}
HudScaleSpinner(hud, "customScale")
}
}
} else {
if (hud.resizeAxes == HudResize.Both) {
Section("Scale", query) {
HudScaleSpinner(hud, "textScale")
}
}
Section("GUI Scale", query, "Use Minecraft GUI Scale", "Custom Scale") {
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
HudSettingTarget(hud, "useGuiScale") {
Expand Down
Loading