Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,25 @@ abstract class ComposeScreen(
}
}

private inline fun sendKeyEventSafely(build: () -> androidx.compose.ui.input.key.KeyEvent): Boolean {
val event = try {
build()
} catch (t: Throwable) {
ComposeSupport.recordSceneFailure(t)
reportUnavailableAndClose()
return false
}
return withScene { it.sendKeyEvent(event) } ?: false
}

private fun poisonScene(cause: Throwable) {
if (scenePoisoned) return
scenePoisoned = true
SkiaCtx.clearComposeFrame()
try {
SkiaCtx.clearComposeFrame()
} catch (t: Throwable) {
cause.addSuppressed(t)
}
LOGGER.error(
"Compose scene for ${this::class.java.simpleName} failed and has been discarded; " +
"it will be rebuilt on the next frame.",
Expand Down Expand Up @@ -392,22 +407,29 @@ abstract class ComposeScreen(

if (liveScene() == null) return

val renderBlock = Runnable {
val canvas = SkiaCtx.canvas
val pixelRatio = surfaceScale()
val mode = OneConfigConfig.reducedResFilter
val amount = OneConfigConfig.uiSharpening
val filter = mode != 0 && amount > 0f &&
DesktopHelper.isMac && osUpscaleFactor() > 1.05f
val depth = canvas.save()
if (filter) canvas.saveLayer(null, filterPaint(mode, amount))
if (pixelRatio != 1f) {
canvas.scale(pixelRatio, pixelRatio)
}
if (withScene { it.render(canvas.asComposeCanvas(), System.nanoTime()) } != null) {
sceneRebuilds = 0
val renderBlock = Runnable {
try {
val canvas = SkiaCtx.canvas
val pixelRatio = surfaceScale()
val mode = OneConfigConfig.reducedResFilter
val amount = OneConfigConfig.uiSharpening
val filter = mode != 0 && amount > 0f &&
DesktopHelper.isMac && osUpscaleFactor() > 1.05f
val depth = canvas.save()
try {
if (filter) canvas.saveLayer(null, filterPaint(mode, amount))
if (pixelRatio != 1f) {
canvas.scale(pixelRatio, pixelRatio)
}
if (withScene { it.render(canvas.asComposeCanvas(), System.nanoTime()) } != null) {
sceneRebuilds = 0
}
} finally {
canvas.restoreToCount(depth)
}
} catch (t: Throwable) {
poisonScene(t)
}
canvas.restoreToCount(depth)
}

val wasDirty = sceneDirty
Expand Down Expand Up @@ -504,26 +526,26 @@ abstract class ComposeScreen(
val eventLocation = KeyEvent.KEY_LOCATION_UNKNOWN
val eventCode = 0

val composeEvent = androidx.compose.ui.input.key.KeyEvent(
key = androidx.compose.ui.input.key.Key(awtCode),
type = KeyEventType.KeyDown,
codePoint = codepoint,
isCtrlPressed = modifiers.ctrlDown(),
isShiftPressed = modifiers.shiftDown(),
isAltPressed = modifiers.altDown(),
isMetaPressed = modifiers.superDown(),
nativeEvent = KeyEvent(
dummyComponent,
eventType,
System.currentTimeMillis(),
modifiersToAwt(modifiers),
eventCode,
char,
eventLocation
val handled = sendKeyEventSafely {
androidx.compose.ui.input.key.KeyEvent(
key = androidx.compose.ui.input.key.Key(awtCode),
type = KeyEventType.KeyDown,
codePoint = codepoint,
isCtrlPressed = modifiers.ctrlDown(),
isShiftPressed = modifiers.shiftDown(),
isAltPressed = modifiers.altDown(),
isMetaPressed = modifiers.superDown(),
nativeEvent = KeyEvent(
dummyComponent,
eventType,
System.currentTimeMillis(),
modifiersToAwt(modifiers),
eventCode,
char,
eventLocation
)
)
)

val handled = withScene { it.sendKeyEvent(composeEvent) } ?: false
}
//? >= 1.21.10 {
return handled || super.charTyped(event)
//? } else {
Expand Down Expand Up @@ -552,28 +574,28 @@ abstract class ComposeScreen(
val eventType = KeyEvent.KEY_PRESSED
val eventLocation = glfwKeyLocation(key)

val composeEvent = androidx.compose.ui.input.key.KeyEvent(
key = androidx.compose.ui.input.key.Key(awtCode, eventLocation),
type = KeyEventType.KeyDown,
// Carry the raw GLFW key code so consumers (e.g. KeybindOption) can recover it losslessly;
// the AWT round-trip in the Key collapses unmapped keys to VK_UNDEFINED.
codePoint = key,
isCtrlPressed = modifiers.ctrlDown(),
isShiftPressed = modifiers.shiftDown(),
isAltPressed = modifiers.altDown(),
isMetaPressed = modifiers.superDown(),
nativeEvent = KeyEvent(
dummyComponent,
eventType,
System.currentTimeMillis(),
modifiersToAwt(modifiers),
awtCode,
Char(0),
eventLocation
val handled = sendKeyEventSafely {
androidx.compose.ui.input.key.KeyEvent(
key = androidx.compose.ui.input.key.Key(awtCode, eventLocation),
type = KeyEventType.KeyDown,
// Carry the raw GLFW key code so consumers (e.g. KeybindOption) can recover it losslessly;
// the AWT round-trip in the Key collapses unmapped keys to VK_UNDEFINED.
codePoint = key,
isCtrlPressed = modifiers.ctrlDown(),
isShiftPressed = modifiers.shiftDown(),
isAltPressed = modifiers.altDown(),
isMetaPressed = modifiers.superDown(),
nativeEvent = KeyEvent(
dummyComponent,
eventType,
System.currentTimeMillis(),
modifiersToAwt(modifiers),
awtCode,
Char(0),
eventLocation
)
)
)

val handled = withScene { it.sendKeyEvent(composeEvent) } ?: false
}
//? >= 1.21.10 {
return handled || super.keyPressed(event)
//? } else {
Expand All @@ -592,26 +614,26 @@ abstract class ComposeScreen(
val awtCode = glfwToAwtKeyCode(key)
val eventLocation = glfwKeyLocation(key)

val composeEvent = androidx.compose.ui.input.key.KeyEvent(
key = androidx.compose.ui.input.key.Key(awtCode, eventLocation),
type = KeyEventType.KeyUp,
codePoint = key,
isCtrlPressed = modifiers.ctrlDown(),
isShiftPressed = modifiers.shiftDown(),
isAltPressed = modifiers.altDown(),
isMetaPressed = modifiers.superDown(),
nativeEvent = KeyEvent(
dummyComponent,
KeyEvent.KEY_RELEASED,
System.currentTimeMillis(),
modifiersToAwt(modifiers),
awtCode,
KeyEvent.CHAR_UNDEFINED,
eventLocation
val handled = sendKeyEventSafely {
androidx.compose.ui.input.key.KeyEvent(
key = androidx.compose.ui.input.key.Key(awtCode, eventLocation),
type = KeyEventType.KeyUp,
codePoint = key,
isCtrlPressed = modifiers.ctrlDown(),
isShiftPressed = modifiers.shiftDown(),
isAltPressed = modifiers.altDown(),
isMetaPressed = modifiers.superDown(),
nativeEvent = KeyEvent(
dummyComponent,
KeyEvent.KEY_RELEASED,
System.currentTimeMillis(),
modifiersToAwt(modifiers),
awtCode,
KeyEvent.CHAR_UNDEFINED,
eventLocation
)
)
)

val handled = withScene { it.sendKeyEvent(composeEvent) } ?: false
}
//? >= 1.21.10 {
return handled || super.keyReleased(event)
//? } else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,17 @@ object ComposeSupport {
return "OneConfig's UI can't start on $osLabel: a required library (AWT or Skiko) is " +
"missing from this Java runtime. Try a full JDK/JRE."
}
return null
return awtInitFailure()
}

private fun awtInitFailure(): String? = try {
Class.forName("java.awt.event.KeyEvent", true, ComposeSupport::class.java.classLoader)
null
} catch (t: Throwable) {
LOG.error("AWT failed to initialize on this runtime; the OneConfig UI has been disabled.", t)
"OneConfig's UI can't start on $osLabel: this Java runtime has no working AWT support " +
"(java.awt.Toolkit failed to initialize). Try a full JDK/JRE rather than a headless " +
"or trimmed-down one."
}

private const val DISABLE_PROPERTY = "oneconfig.ui.disable"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,13 @@ object LegacyHudOffscreen {
hud.renderedH = hud.height * scale
val pose = ext.pose()
pose.pushMatrix()
pose.translate(hud.x, hud.y)
if (scale != 1f) pose.scale(scale, scale)
hud.render(ext)
pose.popMatrix()
try {
pose.translate(hud.x, hud.y)
if (scale != 1f) pose.scale(scale, scale)
hud.render(ext)
} finally {
pose.popMatrix()
}
} catch (t: Throwable) {
LOG.debug("legacy hud render (record) failed", t)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,31 @@ import net.minecraft.client.gui.GuiGraphicsExtractor
import org.polyfrost.oneconfig.api.hud.v1.HudManager
import org.polyfrost.oneconfig.api.hud.v1.LegacyHud

private val LOGGER = org.apache.logging.log4j.LogManager.getLogger("OneConfig/HUD-Render")

object LegacyHudRenderer {
/**
* Reused across frames to hold the HUDs drawn this frame. HUD update/render callbacks may add to or
* remove from [HudManager.activeInstances], so that list is never iterated while they run.
*/
private val frame = ArrayList<LegacyHud>()

/**
* HUDs that have already had a failure logged. A HUD that throws usually throws every frame, so only the
* first failure is reported to avoid flooding the log at framerate. Weak so a removed HUD can be collected.
*/
private val reportedFailures =
java.util.Collections.newSetFromMap(java.util.WeakHashMap<LegacyHud, Boolean>())

private fun reportFailure(hud: LegacyHud, e: Throwable) {
if (!reportedFailures.add(hud)) return
LOGGER.error(
"Failed to render HUD ${hud.title}; it was dropped from this frame and further failures " +
"from it will not be logged",
e,
)
}

fun renderLive(graphics: GuiGraphicsExtractor) {
renderLiveHuds(graphics)
if (CompatOverlayRenderer.oneConfigScreenOpen()) CompatOverlayRenderer.render(graphics)
Expand All @@ -33,32 +51,42 @@ object LegacyHudRenderer {
frame.add(hud)
}
for (hud in frame) {
HudManager.updateIfDue(hud)
val hudScale = hud.effectiveScale
val (mw, mh) = hud.frameMinimumSize()
val w = mw * hudScale
val h = mh * hudScale
if (hud.renderedW != w || hud.renderedH != h) {
Snapshot.withMutableSnapshot {
hud.renderedW = w
hud.renderedH = h
try {
HudManager.updateIfDue(hud)
val hudScale = hud.effectiveScale
val (mw, mh) = hud.frameMinimumSize()
val w = mw * hudScale
val h = mh * hudScale
if (hud.renderedW != w || hud.renderedH != h) {
Snapshot.withMutableSnapshot {
hud.renderedW = w
hud.renderedH = h
}
}
//? >= 1.21.8 {
val pose = graphics.pose()
pose.pushMatrix()
try {
pose.translate(hud.x, hud.y)
if (hudScale != 1f) pose.scale(hudScale, hudScale)
hud.render(graphics)
} finally {
pose.popMatrix()
}
//? } else {
/*val pose = graphics.pose()
pose.pushPose()
try {
pose.translate(hud.x.toDouble(), hud.y.toDouble(), 0.0)
if (hudScale != 1f) pose.scale(hudScale, hudScale, 1f)
hud.render(graphics)
} finally {
pose.popPose()
}
*///? }
} catch (e: Throwable) {
reportFailure(hud, e)
}
//? >= 1.21.8 {
val pose = graphics.pose()
pose.pushMatrix()
pose.translate(hud.x, hud.y)
if (hudScale != 1f) pose.scale(hudScale, hudScale)
hud.render(graphics)
pose.popMatrix()
//? } else {
/*val pose = graphics.pose()
pose.pushPose()
pose.translate(hud.x.toDouble(), hud.y.toDouble(), 0.0)
if (hudScale != 1f) pose.scale(hudScale, hudScale, 1f)
hud.render(graphics)
pose.popPose()
*///? }
}
frame.clear()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,20 +70,20 @@ fun DraggableListOption(data: DraggableListOptionData) {

// Current ordered items. When checkable, includes disabled items appended after enabled ones.
var items by remember(data.prop) {
val ordered = propValueList(data.prop).ifEmpty { allOptions }
val ordered = propValueList(data.prop)
val result = if (data.checkable) {
val enabledSet = ordered.toSet()
ordered + allOptions.filter { it !in enabledSet }
} else {
ordered
ordered.ifEmpty { allOptions }
}
mutableStateOf(result.map { ListEntry(idGen[0]++, it) })
}

// Enabled slot ids (checkable only — property value = enabled items in order)
var enabledIds by remember(data.prop) {
if (!data.checkable) return@remember mutableStateOf(emptySet<Int>())
val enabled = propValueList(data.prop).ifEmpty { allOptions }.toSet()
val enabled = propValueList(data.prop).toSet()
mutableStateOf(items.filter { it.value in enabled }.map { it.id }.toSet())
}

Expand Down
Loading