From a221931802bb7d546714b67abb89eb02f9537e45 Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Tue, 11 Aug 2026 18:31:02 -0400 Subject: [PATCH 01/30] Bridge Compose focus into vanilla's GuiEventListener focus graph (Button) Adds a Modifier.focusable() + LayoutNodeFocusAdapter that wraps a focusable LayoutNode as a vanilla GuiEventListener leaf, and overrides children() on ComposeScreen/ComposeContainerScreen to expose all focusable Compose nodes (scoped to the top layer, matching topNode()'s modal-aware input dispatch) through it. Screen already implements Tab/Shift-Tab/arrow-key navigation and ComponentPath-based focus tracking entirely in terms of children() - this one override is what makes that machinery (and anything else that walks GuiEventListener, e.g. Controlify's controller navigation) reach Compose content at all, matching AbstractWidget's nextFocusPath/getRectangle contract so the built-in tab-order and arrow-key nearest-neighbor search work as-is. Wires it end-to-end into Button/ButtonCore: Enter/Space activates a vanilla-focused button (gated on the same focused state the adapter reads/ writes, to avoid double-firing between the broadcast key dispatch and the adapter's own keyPressed fallback), and a FocusRingModifier draws a visible ring. Disabled buttons drop the focusable modifier entirely, mirroring AbstractWidget.active gating disabled widgets out of Tab order. WidgetState's "hovered" axis is renamed to focused() and now takes `isHovered || isFocused`: a keyboard/controller-focused widget gets the same highlight a mouse-hovered one does, rather than a separate axis most themes would never define. All six callers (Button, Slider, Checkbox, Radio, Switch, Tab) migrate to the new name; only Button's argument actually changes. TabContainer's tabs share ButtonCore, so they pick up Tab/arrow navigation and the focus ring for free. Co-Authored-By: Claude Sonnet 5 --- .../archie/gui/ComposeContainerScreen.kt | 10 +++ .../archie/gui/ComposeScreen.kt | 14 +++ .../composables/containers/TabContainer.kt | 4 +- .../archie/gui/composables/input/Button.kt | 61 ++++++++++--- .../archie/gui/composables/input/Checkbox.kt | 2 +- .../archie/gui/composables/input/Radio.kt | 2 +- .../archie/gui/composables/input/Slider.kt | 2 +- .../archie/gui/composables/input/Switch.kt | 2 +- .../gui/composables/theme/TextureStates.kt | 7 +- .../gui/composables/theme/WidgetState.kt | 23 +++-- .../gui/focus/LayoutNodeFocusAdapter.kt | 90 +++++++++++++++++++ .../modifiers/appearance/FocusRingModifier.kt | 55 ++++++++++++ .../gui/modifiers/input/FocusableModifier.kt | 35 ++++++++ 13 files changed, 280 insertions(+), 27 deletions(-) create mode 100644 core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/focus/LayoutNodeFocusAdapter.kt create mode 100644 core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/appearance/FocusRingModifier.kt create mode 100644 core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/input/FocusableModifier.kt diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeContainerScreen.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeContainerScreen.kt index 2212be8ab..485864a0e 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeContainerScreen.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeContainerScreen.kt @@ -13,6 +13,7 @@ import net.kernelpanicsoft.archie.gui.access.SlotHighlightClipProvider import net.kernelpanicsoft.archie.gui.access.SlotLayerDepthProvider import net.kernelpanicsoft.archie.gui.blockentity.LocalBlockEntityState import net.kernelpanicsoft.archie.gui.composables.containers.RootContainer +import net.kernelpanicsoft.archie.gui.focus.collectFocusableChildren import net.kernelpanicsoft.archie.gui.item.ComposeItemContainerMenu import net.kernelpanicsoft.archie.gui.item.LocalItemState import net.kernelpanicsoft.archie.gui.layer.LayerStackManager @@ -28,6 +29,7 @@ import net.kernelpanicsoft.archie.gui.util.extension.processKeyEvent import net.kernelpanicsoft.archie.gui.util.extension.processPointerEvent import net.kernelpanicsoft.archie.gui.util.extension.processScrollEvent import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.gui.components.events.GuiEventListener import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen import net.minecraft.network.chat.Component import net.minecraft.world.entity.player.Inventory @@ -376,6 +378,14 @@ abstract class ComposeContainerScreen>( private fun getTopNode(): LayoutNode? = layerManager.top?.rootNode + // See ComposeScreen.children() for why this one override is enough to bridge Compose's + // `Modifier.focusable` nodes into vanilla's Tab/Shift-Tab/arrow-key navigation and any + // other GuiEventListener-walking consumer (e.g. Controlify). + override fun children(): List { + val topNode = getTopNode() ?: return super.children() + return collectFocusableChildren(topNode) + } + override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { val topNode = getTopNode() ?: return super.mouseClicked(mouseX, mouseY, button) processPointerEvent(topNode, mouseX, mouseY, PointerEventType.GLOBAL_PRESS, true) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeScreen.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeScreen.kt index e4ebdbfe7..2bfce04c1 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeScreen.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeScreen.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.* import androidx.compose.runtime.snapshots.Snapshot import com.mojang.blaze3d.platform.InputConstants import kotlinx.coroutines.* +import net.kernelpanicsoft.archie.gui.focus.collectFocusableChildren import net.kernelpanicsoft.archie.gui.layer.LayerStackManager import net.kernelpanicsoft.archie.gui.layer.LocalLayerManager import net.kernelpanicsoft.archie.gui.layout.* @@ -17,6 +18,7 @@ import net.kernelpanicsoft.archie.gui.util.extension.processKeyEvent import net.kernelpanicsoft.archie.gui.util.extension.processPointerEvent import net.kernelpanicsoft.archie.gui.util.extension.processScrollEvent import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.gui.components.events.GuiEventListener import net.minecraft.client.gui.screens.Screen import net.minecraft.network.chat.Component import org.lwjgl.glfw.GLFW @@ -270,6 +272,18 @@ abstract class ComposeScreen( private fun topNode() = layerManager.top?.rootNode + // Bridges Compose's own focus concept (`Modifier.focusable`) into vanilla's built-in + // GuiEventListener focus graph. Screen already implements Tab/Shift-Tab/arrow-key + // navigation, focus tracking (getFocused/setFocused) and ComponentPath-based dispatch + // entirely in terms of `children()` - overriding just this one method is enough to make + // all of that (plus anything else that walks GuiEventListener, e.g. Controlify's + // controller navigation) reach Compose content. Scoped to the top layer only, matching + // topNode()'s modal-aware input dispatch: a modal's focus stays within the modal. + override fun children(): List { + val top = topNode() ?: return super.children() + return collectFocusableChildren(top) + } + override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { val top = topNode() ?: return super.mouseClicked(mouseX, mouseY, button) processPointerEvent(top, mouseX, mouseY, PointerEventType.GLOBAL_PRESS, global = true) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/TabContainer.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/TabContainer.kt index f9ff390f7..1f4bc72e0 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/TabContainer.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/TabContainer.kt @@ -344,10 +344,10 @@ fun Tab( onClick = { onClick(spec) }, enabled = enabled, modifier = modifier, - ) { isHovered, isPressed -> + ) { isHovered, isPressed, _ -> val stateKey = WidgetState.resolve( composableTheme, variant, - WidgetState.clicked(selected || isPressed), WidgetState.hovered(isHovered), + WidgetState.clicked(selected || isPressed), WidgetState.focused(isHovered), enabled = enabled, ) val state = composableTheme.getState(stateKey, variant) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt index 1a8ec9093..3297dd2f2 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt @@ -12,7 +12,10 @@ import net.kernelpanicsoft.archie.gui.layout.Layout import net.kernelpanicsoft.archie.gui.layout.Renderer import net.kernelpanicsoft.archie.gui.modifiers.Modifier import net.kernelpanicsoft.archie.gui.modifiers.DebugModifier +import net.kernelpanicsoft.archie.gui.modifiers.appearance.focusRing import net.kernelpanicsoft.archie.gui.modifiers.sizeIn +import net.kernelpanicsoft.archie.gui.modifiers.input.focusable +import net.kernelpanicsoft.archie.gui.modifiers.input.onKeyEvent import net.kernelpanicsoft.archie.gui.modifiers.position.offset import net.kernelpanicsoft.archie.gui.nodes.UINode import net.kernelpanicsoft.archie.gui.theme.LocalTheme @@ -21,8 +24,12 @@ import net.kernelpanicsoft.archie.gui.theme.ThemeVariants import net.kernelpanicsoft.archie.gui.util.extension.drawThemeState import net.kernelpanicsoft.archie.gui.util.extension.invoke import net.minecraft.client.gui.GuiGraphics +import org.lwjgl.glfw.GLFW import kotlin.time.Duration.Companion.milliseconds +/** GLFW key codes that activate a focused button, mirroring vanilla `AbstractWidget` activation. */ +private val BUTTON_ACTIVATION_KEYS = intArrayOf(GLFW.GLFW_KEY_ENTER, GLFW.GLFW_KEY_KP_ENTER, GLFW.GLFW_KEY_SPACE) + /** * A standard themed, clickable button. * @@ -54,7 +61,7 @@ fun Button( onClick, modifier, enabled - ) { isHovered, isPressed -> + ) { isHovered, isPressed, isFocused -> val pressOffset = animateInt( targetValue = if (isPressed) 1 else 0, spec = AnimationSpec(durationMillis = 90.milliseconds, easing = Easings.OutCubic), @@ -77,7 +84,7 @@ fun Button( ) = guiGraphics { val stateKey = WidgetState.resolve( composableTheme, variant, - WidgetState.clicked(isPressed), WidgetState.hovered(isHovered), + WidgetState.clicked(isPressed), WidgetState.focused(isHovered || isFocused), enabled = enabled, ) node.renderState = stateKey @@ -104,16 +111,20 @@ fun Button( /** * A stateless clickable container composable. * - * `ButtonCore` manages hover and pressed state internally and exposes them to [content] - * via the lambda parameters. It handles cursor changes and the full pointer-event lifecycle, - * but applies no visual styling of its own — that is left entirely to [content]. + * `ButtonCore` manages hover, pressed and focus state internally and exposes them to + * [content] via the lambda parameters. It handles cursor changes, the full pointer-event + * lifecycle, and vanilla keyboard/controller focus navigation - Tab/Shift-Tab and arrow keys + * (via `Screen.children()`/`nextFocusPath`) can reach and activate it (Enter/Space) exactly + * like a plain `AbstractWidget`, including through controller-navigation mods such as + * Controlify. It applies no visual styling of its own beyond a default focus-ring overlay - + * everything else is left entirely to [content]. * * Use [ButtonCore] when you need custom button visuals. For a standard themed button, use * [Button] instead. * * ### Example * ```kotlin - * ButtonCore(onClick = { println("Clicked!") }) { isHovered, isPressed -> + * ButtonCore(onClick = { println("Clicked!") }) { isHovered, isPressed, isFocused -> * Box( * modifier = Modifier.background(if (isHovered) KColor.LIGHT_GRAY else KColor.GRAY) * .size(80, 20) @@ -123,23 +134,49 @@ fun Button( * } * ``` * - * @param onClick Invoked with the receiving [UINode] when the button is pressed. + * @param onClick Invoked with the receiving [UINode] when the button is pressed (by mouse, + * or by Enter/Space while vanilla-focused). * @param modifier Additional modifiers applied to the outer clickable container. - * @param enabled When `false`, pointer events are ignored and no cursor change occurs. - * @param content The button's visual content, receiving `isHovered` and `isPressed` booleans. + * @param enabled When `false`, pointer and activation-key events are ignored and no cursor + * change occurs. + * @param content The button's visual content, receiving `isHovered`, `isPressed` and + * `isFocused` booleans. */ @Composable fun ButtonCore( onClick: (UINode) -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, - content: @Composable (isHovered: Boolean, isPressed: Boolean) -> Unit, + content: @Composable (isHovered: Boolean, isPressed: Boolean, isFocused: Boolean) -> Unit, ) { + val focused = remember { mutableStateOf(false) } + + // Only a participating widget shows up in ComposeScreen.children() (see + // collectFocusableChildren) at all - mirrors AbstractWidget.nextFocusPath returning null + // while `!active`, which keeps a disabled vanilla widget out of Tab order the same way. + val focusModifier = if (enabled) { + Modifier + .focusable(focused) + .onKeyEvent { node, event -> + if (focused.value && event.keyCode in BUTTON_ACTIVATION_KEYS) { + onClick(node) + event.consume(bypassSuperCall = true) + } + } + .focusRing(focused) + } else { + focused.value = false + Modifier + } + Clickable( onClick = onClick, enabled = enabled, - modifier = Modifier.then(DebugModifier(strs = listOf("Enabled: $enabled"))).then(modifier), + modifier = Modifier + .then(DebugModifier(strs = listOf("Enabled: $enabled"))) + .then(focusModifier) + .then(modifier), ) { isHovered, isPressed -> - content(isHovered, isPressed) + content(isHovered, isPressed, focused.value) } } diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Checkbox.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Checkbox.kt index 82d2a01bd..3d0e31435 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Checkbox.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Checkbox.kt @@ -74,7 +74,7 @@ fun Checkbox( ) = guiGraphics { val stateKey = WidgetState.resolve( composableTheme, variant, - WidgetState.clicked(checked), WidgetState.hovered(isHovered), + WidgetState.clicked(checked), WidgetState.focused(isHovered), ) node.renderState = stateKey val state = composableTheme.getState(stateKey, variant) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Radio.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Radio.kt index 2ef5cd187..9091f59db 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Radio.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Radio.kt @@ -106,7 +106,7 @@ fun RadioButton( ) = guiGraphics { val stateKey = WidgetState.resolve( composableTheme, variant, - WidgetState.clicked(currentSelected), WidgetState.hovered(hovered), + WidgetState.clicked(currentSelected), WidgetState.focused(hovered), enabled = enabled, ) node.renderState = stateKey diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt index 149d01735..a1ff330e0 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt @@ -49,7 +49,7 @@ internal fun resolveSliderThumbX(rawThumbX: Int, sliderX: Int, sliderWidth: Int, } private fun resolveSliderStateName(theme: ComposableTheme, variant: String, enabled: Boolean, hovered: Boolean, dragging: Boolean): String = - WidgetState.resolve(theme, variant, WidgetState.clicked(dragging), WidgetState.hovered(hovered), enabled = enabled) + WidgetState.resolve(theme, variant, WidgetState.clicked(dragging), WidgetState.focused(hovered), enabled = enabled) /** * Low-level unstyled slider behavior: drag/click-to-position and hover/drag state tracking, diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Switch.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Switch.kt index 1ee370a11..1a8cb21a5 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Switch.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Switch.kt @@ -110,7 +110,7 @@ fun Switch( ) = guiGraphics { val trackStateKey = WidgetState.resolve( trackTheme, variant, - WidgetState.clicked(currentChecked), WidgetState.hovered(hovered), + WidgetState.clicked(currentChecked), WidgetState.focused(hovered), enabled = enabled, ) node.renderState = trackStateKey diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/theme/TextureStates.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/theme/TextureStates.kt index 26404864d..9f64e5f1a 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/theme/TextureStates.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/theme/TextureStates.kt @@ -15,7 +15,12 @@ object TextureStates { /** Used when the composable is disabled and cannot be interacted with. */ const val DISABLED = "disabled" - /** Used when the mouse cursor is hovering over the composable. */ + /** + * Used when the composable is highlighted - the mouse cursor is hovering over it, or (see + * [WidgetState.focused]) it holds vanilla keyboard/controller focus. Both count as the same + * texture state: there's one "this is the thing about to be interacted with" visual, + * regardless of which input method put it there. + */ const val HOVERED = "hovered" /** Used when the composable has been activated/checked/clicked (toggle state). */ diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/theme/WidgetState.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/theme/WidgetState.kt index 962570da7..7b362e04c 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/theme/WidgetState.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/theme/WidgetState.kt @@ -14,7 +14,7 @@ import net.kernelpanicsoft.archie.gui.theme.ComposableTheme * ```kotlin * val stateKey = WidgetState.resolve( * composableTheme, variant, - * WidgetState.clicked(checked), WidgetState.hovered(hovered), + * WidgetState.clicked(checked), WidgetState.focused(hovered || vanillaFocused), * enabled = enabled, * ) * node.renderState = stateKey @@ -23,15 +23,22 @@ import net.kernelpanicsoft.archie.gui.theme.ComposableTheme */ object WidgetState { /** - * One named boolean axis of a widget's interaction state (e.g. "hovered" paired with - * whether the pointer currently is), in the priority order [resolve] should consider it - + * One named boolean axis of a widget's interaction state (e.g. "focused" paired with + * whether the widget currently is), in the priority order [resolve] should consider it - * pass axes to [resolve] most-significant first (typically the "activated" axis - checked/ - * selected/pressed - before "hovered"). + * selected/pressed - before "focused"). */ data class Axis(val name: String, val active: Boolean) - /** An [Axis] for [TextureStates.HOVERED]. */ - fun hovered(active: Boolean) = Axis(TextureStates.HOVERED, active) + /** + * An [Axis] for [TextureStates.HOVERED] - whether the widget is currently highlighted, + * meaning either the mouse is hovering it *or* it holds vanilla keyboard/controller focus + * (see `Modifier.focusable`). Both drive the same texture state: there's one "this is the + * thing about to be interacted with" visual regardless of which input method put it there, + * so callers that support both pass a single merged boolean (e.g. `isHovered || isFocused`) + * rather than two independent axes. + */ + fun focused(active: Boolean) = Axis(TextureStates.HOVERED, active) /** An [Axis] for [TextureStates.CLICKED] - a checkbox/switch/radio's checked-or-selected state, a button/tab's pressed-or-selected state, or a slider's dragging state. */ fun clicked(active: Boolean) = Axis(TextureStates.CLICKED, active) @@ -46,7 +53,7 @@ object WidgetState { * `!enabled` where a `disabled` theme state actually exists to show. * - Otherwise, tries the most specific composite key first: every currently-active axis's * [Axis.name], joined by `"_and_"` in priority order (e.g. `"clicked_and_hovered"` for - * [clicked]+[hovered] both active). If [theme] doesn't define that combination, falls + * [clicked]+[focused] both active). If [theme] doesn't define that combination, falls * back one axis at a time - by priority, i.e. trying each individual active axis's own * key alone, highest priority first - stopping at the first one [theme] defines. * - Returns [TextureStates.DEFAULT] if no active axis (alone or combined) has a defined @@ -62,7 +69,7 @@ object WidgetState { * `selected || isPressed`, but only paired it with `hovered` into the combined state when * specifically `selected` was true - a pressed-but-unselected-and-hovered tab silently lost * its hover visual. Callers migrating to this resolver should pass a single `clicked` axis - * (`selected || isPressed`) and a separate `hovered` axis as normal; [resolve] then treats + * (`selected || isPressed`) and a separate `focused` axis as normal; [resolve] then treats * both uniformly like every other component, which is a deliberate behavior fix, not an * incidental one. */ diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/focus/LayoutNodeFocusAdapter.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/focus/LayoutNodeFocusAdapter.kt new file mode 100644 index 000000000..f3c920415 --- /dev/null +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/focus/LayoutNodeFocusAdapter.kt @@ -0,0 +1,90 @@ +package net.kernelpanicsoft.archie.gui.focus + +import net.kernelpanicsoft.archie.gui.layout.LayoutNode +import net.kernelpanicsoft.archie.gui.modifiers.input.CharEvent +import net.kernelpanicsoft.archie.gui.modifiers.input.FocusableModifier +import net.kernelpanicsoft.archie.gui.modifiers.input.KeyEvent +import net.kernelpanicsoft.archie.gui.modifiers.input.OnCharTypedModifier +import net.kernelpanicsoft.archie.gui.modifiers.input.OnKeyEventModifier +import net.minecraft.client.gui.ComponentPath +import net.minecraft.client.gui.components.events.GuiEventListener +import net.minecraft.client.gui.navigation.FocusNavigationEvent +import net.minecraft.client.gui.navigation.ScreenRectangle + +/** + * Bridges one [FocusableModifier]-carrying [LayoutNode] into vanilla Minecraft's + * `GuiEventListener` focus graph, so vanilla's Tab/Shift-Tab and arrow-key navigation - and + * anything else that walks `GuiEventListener`, e.g. Controlify's controller navigation - + * reaches Compose content the same way it reaches an ordinary `AbstractWidget`. + * + * `ComposeScreen`/`ComposeContainerScreen` rebuild a fresh list of adapters on every + * `children()` call, since the [LayoutNode] tree can change between frames. Vanilla's own + * `ContainerEventHandler.handleTabNavigation` looks up the previously-focused + * `GuiEventListener` by `indexOf` in that freshly rebuilt list, so [equals]/[hashCode] are + * keyed on the wrapped node's identity rather than the adapter's - two adapters wrapping the + * same node must compare equal across separate `children()` calls, or Tab would never be able + * to tell "the currently focused thing" apart from "a brand new list entry" and always restart + * from the beginning. + */ +class LayoutNodeFocusAdapter(val node: LayoutNode) : GuiEventListener { + + private val focusable: FocusableModifier? + get() = node.get() + + override fun equals(other: Any?): Boolean = other is LayoutNodeFocusAdapter && other.node === node + override fun hashCode(): Int = System.identityHashCode(node) + + override fun getRectangle(): ScreenRectangle { + val (x, y) = node.absoluteCoords + return ScreenRectangle(x, y, node.width, node.height) + } + + override fun isFocused(): Boolean = focusable?.focused?.value == true + + override fun setFocused(focused: Boolean) { + focusable?.focused?.value = focused + } + + // The GuiEventListener default always returns null - AbstractWidget overrides it the same + // way, to actually offer itself as a leaf when not already focused. Without this override, + // ContainerEventHandler's Tab/arrow-key search (which calls nextFocusPath on every + // candidate from children(), not just membership-checks it) would never select anything + // here, silently leaving Tab navigation a no-op. + override fun nextFocusPath(event: FocusNavigationEvent): ComponentPath? = + if (!isFocused()) ComponentPath.leaf(this) else null + + /** Forwards to [node]'s own [OnKeyEventModifier] chain - the same one the broadcast key dispatch uses. */ + override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { + val event = KeyEvent(keyCode, scanCode, modifiers) + node.modifier.foldIn(Unit) { _, el -> + if (el is OnKeyEventModifier && !event.isConsumed) el.onEvent(node, event) + } + return event.isConsumed + } + + /** Forwards to [node]'s own [OnCharTypedModifier] chain - the same one the broadcast char dispatch uses. */ + override fun charTyped(codePoint: Char, modifiers: Int): Boolean { + val event = CharEvent(codePoint, modifiers) + node.modifier.foldIn(Unit) { _, el -> + if (el is OnCharTypedModifier && !event.isConsumed) el.onEvent(node, event) + } + return event.isConsumed + } +} + +/** + * Collects every [FocusableModifier]-carrying [LayoutNode] in [root]'s subtree, each wrapped + * as a [LayoutNodeFocusAdapter], in composition (depth-first, declaration) order - the order + * vanilla's Tab-navigation (`ContainerEventHandler.handleTabNavigation`) walks by default. + * + * Backs `ComposeScreen`/`ComposeContainerScreen`'s `children()` override, the one hook that + * lets vanilla's built-in Tab/Shift-Tab/arrow-key navigation - and anything else that walks + * `GuiEventListener`, e.g. Controlify - see Compose content at all. + */ +internal fun collectFocusableChildren(root: LayoutNode): List = buildList { + fun visit(node: LayoutNode) { + if (node.get() != null) add(LayoutNodeFocusAdapter(node)) + node.children.toList().forEach(::visit) + } + visit(root) +} diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/appearance/FocusRingModifier.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/appearance/FocusRingModifier.kt new file mode 100644 index 000000000..5e6fac20c --- /dev/null +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/appearance/FocusRingModifier.kt @@ -0,0 +1,55 @@ +package net.kernelpanicsoft.archie.gui.modifiers.appearance + +import androidx.compose.runtime.State +import androidx.compose.runtime.Stable +import net.kernelpanicsoft.archie.gui.modifiers.ContentDrawScope +import net.kernelpanicsoft.archie.gui.modifiers.DrawModifier +import net.kernelpanicsoft.archie.gui.modifiers.Modifier +import net.kernelpanicsoft.archie.gui.util.KColor +import net.kernelpanicsoft.archie.gui.util.extension.drawRectOutline + +/** + * A [DrawModifier] that outlines a composable while [focused] is `true` - the visible + * counterpart to [net.kernelpanicsoft.archie.gui.modifiers.input.focusable], giving + * keyboard/controller users the same "what's focused" feedback vanilla widgets draw for free. + * + * Drawn *after* the node's own content (unlike [BorderModifier], which draws first) so the + * ring sits on top rather than being obscured by the content it's outlining. + * + * @property color ARGB packed ring colour. + * @property thickness Ring stroke width in pixels. + */ +data class FocusRingModifier( + val focused: State, + val color: Int, + val thickness: Int, +) : Modifier.Element, DrawModifier { + + override fun mergeWith(other: FocusRingModifier): FocusRingModifier = other + + override fun ContentDrawScope.draw() { + drawContent() + if (focused.value) guiGraphics.drawRectOutline(x, y, width, height, color, thickness) + } + + override fun toString(): String = "FocusRingModifier(focused=${focused.value})" +} + +/** + * Draws a [color] outline around this composable while [focused] is `true`. + * + * @param color The ring colour. + * @param thickness The ring stroke width in pixels (default 1). + */ +@Stable +fun Modifier.focusRing(focused: State, color: KColor = KColor.YELLOW, thickness: Int = 1): Modifier = + this then FocusRingModifier(focused, color.argb, thickness) + +/** + * Draws an outline around this composable while [focused] is `true`, using a raw ARGB [color]. + * + * @param thickness The ring stroke width in pixels (default 1). + */ +@Stable +fun Modifier.focusRing(focused: State, color: Int, thickness: Int = 1): Modifier = + this then FocusRingModifier(focused, color, thickness) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/input/FocusableModifier.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/input/FocusableModifier.kt new file mode 100644 index 000000000..642f5a038 --- /dev/null +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/input/FocusableModifier.kt @@ -0,0 +1,35 @@ +package net.kernelpanicsoft.archie.gui.modifiers.input + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.Stable +import net.kernelpanicsoft.archie.gui.modifiers.Modifier + +/** + * Marks a composable as a stop in vanilla Minecraft's built-in focus-navigation graph. + * + * A node carrying this modifier is exposed as a synthetic `GuiEventListener` leaf from + * `ComposeScreen`/`ComposeContainerScreen`'s `children()` override (see + * `net.kernelpanicsoft.archie.gui.focus.LayoutNodeFocusAdapter`), so vanilla's own Tab/ + * Shift-Tab and arrow-key navigation - and anything else that walks `GuiEventListener`, e.g. + * Controlify's controller-driven `ScreenProcessor` - can reach it exactly like an ordinary + * `AbstractWidget`. + * + * @property focused Backing focus state, shared with the adapter: vanilla writes to it via + * `setFocused`, and the owning composable reads it to render a focus indicator or gate + * activation (see [onKeyEvent]). + */ +data class FocusableModifier( + val focused: MutableState, +) : Modifier.Element { + override fun mergeWith(other: FocusableModifier): FocusableModifier = other + override fun toString(): String = "FocusableModifier(focused=${focused.value})" +} + +/** + * Registers this composable as a vanilla focus-navigation stop, backed by [focused]. + * + * Combine with [onKeyEvent], gated on `focused.value`, to react to Enter/Space while + * vanilla-focused - mirroring how a plain `AbstractWidget` handles activation. + */ +@Stable +fun Modifier.focusable(focused: MutableState): Modifier = this then FocusableModifier(focused) From 916f8f5de2bc2cdf84bb73c2778b1d1a545f4682 Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Tue, 11 Aug 2026 18:50:08 -0400 Subject: [PATCH 02/30] Rename hovered/HOVERED texture state to focused/FOCUSED throughout Follow-up to the previous commit's WidgetState.hovered -> focused rename: that only changed the Kotlin-side accessor while keeping the underlying TextureStates string key as "hovered" for lookup compatibility. This commit finishes the rename for real - TextureStates.HOVERED/CLICKED_AND_HOVERED become FOCUSED/CLICKED_AND_FOCUSED ("focused"/"clicked_and_focused"), and every theme JSON's matching state key is renamed to match (button, checkbox, radio, slider, slider_handle, switch_track, tab_game, tab_menu). Texture identifiers that already followed a "_hovered" naming pattern are renamed alongside their state key (checkbox/radio/switch_track's hovered + clicked_and_hovered art, tab_game/tab_menu's hovered art) via `git mv`, with their .mcmeta nine-slice companions renamed to match. Texture identifiers using an unrelated existing convention (button_highlighted, slider_highlighted, slider_handle_highlighted, tab_game/tab_menu's clicked_and_focused -> *_selected_highlighted) are left alone - only the JSON state *key* changes for those, since the texture path itself is a free-form identifier that never had to match the state name. Also fixes the two other consumers of the renamed constants that would otherwise have failed to compile: InputComponentsGameTest's render-state assertions and ComposeScreenTestContext's hover() doc comment, plus docs/gametest.md's matching example and prose. Co-Authored-By: Claude Sonnet 5 --- .../archie/gui/composables/input/Button.kt | 2 +- .../archie/gui/composables/theme/TextureStates.kt | 8 ++++---- .../archie/gui/composables/theme/WidgetState.kt | 10 +++++----- .../kernelpanicsoft/archie/gui/nodes/UINode.kt | 2 +- .../archie/gui/theme/ComposableTheme.kt | 2 +- .../assets/archie/archie_themes/java/button.json | 2 +- .../archie/archie_themes/java/checkbox.json | 8 ++++---- .../assets/archie/archie_themes/java/radio.json | 8 ++++---- .../assets/archie/archie_themes/java/slider.json | 2 +- .../archie/archie_themes/java/slider_handle.json | 2 +- .../archie/archie_themes/java/switch_track.json | 8 ++++---- .../archie/archie_themes/java/tab_game.json | 6 +++--- .../archie/archie_themes/java/tab_menu.json | 6 +++--- ...vered.png => checkbox_clicked_and_focused.png} | Bin ...{checkbox_hovered.png => checkbox_focused.png} | Bin ..._hovered.png => radio_clicked_and_focused.png} | Bin .../java/{radio_hovered.png => radio_focused.png} | Bin ...d.png => switch_track_clicked_and_focused.png} | Bin ...track_hovered.png => switch_track_focused.png} | Bin ...vered.png => tab_game_clicked_and_focused.png} | Bin ...ta => tab_game_clicked_and_focused.png.mcmeta} | 0 ...{tab_game_hovered.png => tab_game_focused.png} | Bin ...red.png.mcmeta => tab_game_focused.png.mcmeta} | 0 ...vered.png => tab_menu_clicked_and_focused.png} | Bin ...ta => tab_menu_clicked_and_focused.png.mcmeta} | 0 ...{tab_menu_hovered.png => tab_menu_focused.png} | Bin ...red.png.mcmeta => tab_menu_focused.png.mcmeta} | 0 docs/gametest.md | 6 +++--- .../archie/gametest/ComposeScreenTestContext.kt | 2 +- .../internal/tests/InputComponentsGameTest.kt | 14 +++++++------- 30 files changed, 44 insertions(+), 44 deletions(-) rename core/common/src/main/resources/assets/archie/textures/gui/sprites/java/{checkbox_clicked_and_hovered.png => checkbox_clicked_and_focused.png} (100%) rename core/common/src/main/resources/assets/archie/textures/gui/sprites/java/{checkbox_hovered.png => checkbox_focused.png} (100%) rename core/common/src/main/resources/assets/archie/textures/gui/sprites/java/{radio_clicked_and_hovered.png => radio_clicked_and_focused.png} (100%) rename core/common/src/main/resources/assets/archie/textures/gui/sprites/java/{radio_hovered.png => radio_focused.png} (100%) rename core/common/src/main/resources/assets/archie/textures/gui/sprites/java/{switch_track_clicked_and_hovered.png => switch_track_clicked_and_focused.png} (100%) rename core/common/src/main/resources/assets/archie/textures/gui/sprites/java/{switch_track_hovered.png => switch_track_focused.png} (100%) rename core/common/src/main/resources/assets/archie/textures/gui/sprites/java/{tab_game_clicked_and_hovered.png => tab_game_clicked_and_focused.png} (100%) rename core/common/src/main/resources/assets/archie/textures/gui/sprites/java/{tab_game_clicked_and_hovered.png.mcmeta => tab_game_clicked_and_focused.png.mcmeta} (100%) rename core/common/src/main/resources/assets/archie/textures/gui/sprites/java/{tab_game_hovered.png => tab_game_focused.png} (100%) rename core/common/src/main/resources/assets/archie/textures/gui/sprites/java/{tab_game_hovered.png.mcmeta => tab_game_focused.png.mcmeta} (100%) rename core/common/src/main/resources/assets/archie/textures/gui/sprites/java/{tab_menu_clicked_and_hovered.png => tab_menu_clicked_and_focused.png} (100%) rename core/common/src/main/resources/assets/archie/textures/gui/sprites/java/{tab_menu_clicked_and_hovered.png.mcmeta => tab_menu_clicked_and_focused.png.mcmeta} (100%) rename core/common/src/main/resources/assets/archie/textures/gui/sprites/java/{tab_menu_hovered.png => tab_menu_focused.png} (100%) rename core/common/src/main/resources/assets/archie/textures/gui/sprites/java/{tab_menu_hovered.png.mcmeta => tab_menu_focused.png.mcmeta} (100%) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt index 3297dd2f2..75e01c146 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt @@ -33,7 +33,7 @@ private val BUTTON_ACTIVATION_KEYS = intArrayOf(GLFW.GLFW_KEY_ENTER, GLFW.GLFW_K /** * A standard themed, clickable button. * - * Renders the themed [texture] state ([TextureStates.DEFAULT]/[TextureStates.HOVERED]/ + * Renders the themed [texture] state ([TextureStates.DEFAULT]/[TextureStates.FOCUSED]/ * [TextureStates.CLICKED]/[TextureStates.DISABLED]) behind [content], animating a 1px press * offset while held. For fully custom visuals, use [ButtonCore] directly instead. * diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/theme/TextureStates.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/theme/TextureStates.kt index 9f64e5f1a..53ae60239 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/theme/TextureStates.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/theme/TextureStates.kt @@ -6,7 +6,7 @@ import net.kernelpanicsoft.archie.gui.theme.ThemeState * Constant keys used to look up [ThemeState] entries within a [ComposableTheme]'s state map. * * Composables use these keys to select the correct texture variant based on their current - * interactive state (e.g. hovered, pressed, disabled). + * interactive state (e.g. focused, pressed, disabled). */ object TextureStates { /** The default idle state used when no other state applies. */ @@ -21,11 +21,11 @@ object TextureStates { * texture state: there's one "this is the thing about to be interacted with" visual, * regardless of which input method put it there. */ - const val HOVERED = "hovered" + const val FOCUSED = "focused" /** Used when the composable has been activated/checked/clicked (toggle state). */ const val CLICKED = "clicked" - /** Used when the composable is both activated and hovered simultaneously. */ - const val CLICKED_AND_HOVERED = "clicked_and_hovered" + /** Used when the composable is both activated and focused simultaneously. */ + const val CLICKED_AND_FOCUSED = "clicked_and_focused" } diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/theme/WidgetState.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/theme/WidgetState.kt index 7b362e04c..52f795bd7 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/theme/WidgetState.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/theme/WidgetState.kt @@ -31,14 +31,14 @@ object WidgetState { data class Axis(val name: String, val active: Boolean) /** - * An [Axis] for [TextureStates.HOVERED] - whether the widget is currently highlighted, + * An [Axis] for [TextureStates.FOCUSED] - whether the widget is currently highlighted, * meaning either the mouse is hovering it *or* it holds vanilla keyboard/controller focus * (see `Modifier.focusable`). Both drive the same texture state: there's one "this is the * thing about to be interacted with" visual regardless of which input method put it there, * so callers that support both pass a single merged boolean (e.g. `isHovered || isFocused`) * rather than two independent axes. */ - fun focused(active: Boolean) = Axis(TextureStates.HOVERED, active) + fun focused(active: Boolean) = Axis(TextureStates.FOCUSED, active) /** An [Axis] for [TextureStates.CLICKED] - a checkbox/switch/radio's checked-or-selected state, a button/tab's pressed-or-selected state, or a slider's dragging state. */ fun clicked(active: Boolean) = Axis(TextureStates.CLICKED, active) @@ -52,7 +52,7 @@ object WidgetState { * weren't a factor - matching every existing chain's behavior of only branching on * `!enabled` where a `disabled` theme state actually exists to show. * - Otherwise, tries the most specific composite key first: every currently-active axis's - * [Axis.name], joined by `"_and_"` in priority order (e.g. `"clicked_and_hovered"` for + * [Axis.name], joined by `"_and_"` in priority order (e.g. `"clicked_and_focused"` for * [clicked]+[focused] both active). If [theme] doesn't define that combination, falls * back one axis at a time - by priority, i.e. trying each individual active axis's own * key alone, highest priority first - stopping at the first one [theme] defines. @@ -61,12 +61,12 @@ object WidgetState { * * This graceful per-axis fallback (rather than jumping straight from the full composite to * [TextureStates.DEFAULT]) generalizes what `Button`'s chain alone used to do by hand - * (falling through a missing "clicked" state to "hovered" - `button.json` defines no + * (falling through a missing "clicked" state to "focused" - `button.json` defines no * "clicked" state at all) - every caller gets it for free, without needing its own * `hasState` check. * * **Tab note:** `TabContainer.kt`'s old chain computed its "clicked" axis from - * `selected || isPressed`, but only paired it with `hovered` into the combined state when + * `selected || isPressed`, but only paired it with `focused` into the combined state when * specifically `selected` was true - a pressed-but-unselected-and-hovered tab silently lost * its hover visual. Callers migrating to this resolver should pass a single `clicked` axis * (`selected || isPressed`) and a separate `focused` axis as normal; [resolve] then treats diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/nodes/UINode.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/nodes/UINode.kt index e066a0da1..b23ebdf28 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/nodes/UINode.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/nodes/UINode.kt @@ -35,7 +35,7 @@ interface UINode { /** * The [net.kernelpanicsoft.archie.gui.composables.theme.TextureStates] key a stateful - * [Renderer] most recently selected to draw (e.g. `"hovered"`, `"clicked_and_hovered"`), + * [Renderer] most recently selected to draw (e.g. `"focused"`, `"clicked_and_focused"`), * or `null` for nodes that don't render theme-state-driven visuals. * * Set by the [Renderer] itself, purely as a test hook - lets a client GameTest assert which diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/theme/ComposableTheme.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/theme/ComposableTheme.kt index cf7feb180..631b2428e 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/theme/ComposableTheme.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/theme/ComposableTheme.kt @@ -155,7 +155,7 @@ data class ComposableTheme( * "width": 64, * "height": 20 * }, - * "hovered": { "texture": "archie:java/button_highlighted" } + * "focused": { "texture": "archie:java/button_highlighted" } * } * } * ``` diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/button.json b/core/common/src/main/resources/assets/archie/archie_themes/java/button.json index 6a445ec51..29aefb904 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/java/button.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/button.json @@ -9,7 +9,7 @@ "width": 64, "height": 20 }, - "hovered": { + "focused": { "texture": "archie:java/button_highlighted" }, "disabled": { diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/checkbox.json b/core/common/src/main/resources/assets/archie/archie_themes/java/checkbox.json index 2cb0de488..3970a2890 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/java/checkbox.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/checkbox.json @@ -9,14 +9,14 @@ "width": 20, "height": 20 }, - "hovered": { - "texture": "archie:java/checkbox_hovered" + "focused": { + "texture": "archie:java/checkbox_focused" }, "clicked": { "texture": "archie:java/checkbox_clicked" }, - "clicked_and_hovered": { - "texture": "archie:java/checkbox_clicked_and_hovered" + "clicked_and_focused": { + "texture": "archie:java/checkbox_clicked_and_focused" } } } diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/radio.json b/core/common/src/main/resources/assets/archie/archie_themes/java/radio.json index 7540bd2a6..6c69c1079 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/java/radio.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/radio.json @@ -9,14 +9,14 @@ "width": 20, "height": 20 }, - "hovered": { - "texture": "archie:java/radio_hovered" + "focused": { + "texture": "archie:java/radio_focused" }, "clicked": { "texture": "archie:java/radio_clicked" }, - "clicked_and_hovered": { - "texture": "archie:java/radio_clicked_and_hovered" + "clicked_and_focused": { + "texture": "archie:java/radio_clicked_and_focused" }, "disabled": { "texture": "archie:java/radio_disabled" diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/slider.json b/core/common/src/main/resources/assets/archie/archie_themes/java/slider.json index dfd75c3ff..6cdff4c73 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/java/slider.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/slider.json @@ -9,7 +9,7 @@ "width": 200, "height": 20 }, - "hovered": { + "focused": { "texture": "archie:java/slider_highlighted" }, "clicked": { diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/slider_handle.json b/core/common/src/main/resources/assets/archie/archie_themes/java/slider_handle.json index 1b2f9943b..081e973bf 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/java/slider_handle.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/slider_handle.json @@ -9,7 +9,7 @@ "width": 8, "height": 20 }, - "hovered": { + "focused": { "texture": "archie:java/slider_handle_highlighted" }, "clicked": { diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/switch_track.json b/core/common/src/main/resources/assets/archie/archie_themes/java/switch_track.json index 43d558043..40143f1ce 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/java/switch_track.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/switch_track.json @@ -9,14 +9,14 @@ "width": 34, "height": 18 }, - "hovered": { - "texture": "archie:java/switch_track_hovered" + "focused": { + "texture": "archie:java/switch_track_focused" }, "clicked": { "texture": "archie:java/switch_track_clicked" }, - "clicked_and_hovered": { - "texture": "archie:java/switch_track_clicked_and_hovered" + "clicked_and_focused": { + "texture": "archie:java/switch_track_clicked_and_focused" }, "disabled": { "texture": "archie:java/switch_track_disabled" diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/tab_game.json b/core/common/src/main/resources/assets/archie/archie_themes/java/tab_game.json index 5cf2141c8..3415f1fd3 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/java/tab_game.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/tab_game.json @@ -9,13 +9,13 @@ "width": 26, "height": 32 }, - "hovered": { - "texture": "archie:java/tab_game_hovered" + "focused": { + "texture": "archie:java/tab_game_focused" }, "clicked": { "texture": "archie:java/tab_game_selected" }, - "clicked_and_hovered": { + "clicked_and_focused": { "texture": "archie:java/tab_game_selected_highlighted" }, "disabled": { diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/tab_menu.json b/core/common/src/main/resources/assets/archie/archie_themes/java/tab_menu.json index f005d6b8c..3bc17c9fe 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/java/tab_menu.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/tab_menu.json @@ -9,13 +9,13 @@ "width": 130, "height": 24 }, - "hovered": { - "texture": "archie:java/tab_menu_hovered" + "focused": { + "texture": "archie:java/tab_menu_focused" }, "clicked": { "texture": "archie:java/tab_menu_selected" }, - "clicked_and_hovered": { + "clicked_and_focused": { "texture": "archie:java/tab_menu_selected_highlighted" }, "disabled": { diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/checkbox_clicked_and_hovered.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/checkbox_clicked_and_focused.png similarity index 100% rename from core/common/src/main/resources/assets/archie/textures/gui/sprites/java/checkbox_clicked_and_hovered.png rename to core/common/src/main/resources/assets/archie/textures/gui/sprites/java/checkbox_clicked_and_focused.png diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/checkbox_hovered.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/checkbox_focused.png similarity index 100% rename from core/common/src/main/resources/assets/archie/textures/gui/sprites/java/checkbox_hovered.png rename to core/common/src/main/resources/assets/archie/textures/gui/sprites/java/checkbox_focused.png diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/radio_clicked_and_hovered.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/radio_clicked_and_focused.png similarity index 100% rename from core/common/src/main/resources/assets/archie/textures/gui/sprites/java/radio_clicked_and_hovered.png rename to core/common/src/main/resources/assets/archie/textures/gui/sprites/java/radio_clicked_and_focused.png diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/radio_hovered.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/radio_focused.png similarity index 100% rename from core/common/src/main/resources/assets/archie/textures/gui/sprites/java/radio_hovered.png rename to core/common/src/main/resources/assets/archie/textures/gui/sprites/java/radio_focused.png diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/switch_track_clicked_and_hovered.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/switch_track_clicked_and_focused.png similarity index 100% rename from core/common/src/main/resources/assets/archie/textures/gui/sprites/java/switch_track_clicked_and_hovered.png rename to core/common/src/main/resources/assets/archie/textures/gui/sprites/java/switch_track_clicked_and_focused.png diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/switch_track_hovered.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/switch_track_focused.png similarity index 100% rename from core/common/src/main/resources/assets/archie/textures/gui/sprites/java/switch_track_hovered.png rename to core/common/src/main/resources/assets/archie/textures/gui/sprites/java/switch_track_focused.png diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_clicked_and_hovered.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_clicked_and_focused.png similarity index 100% rename from core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_clicked_and_hovered.png rename to core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_clicked_and_focused.png diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_clicked_and_hovered.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_clicked_and_focused.png.mcmeta similarity index 100% rename from core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_clicked_and_hovered.png.mcmeta rename to core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_clicked_and_focused.png.mcmeta diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_hovered.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_focused.png similarity index 100% rename from core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_hovered.png rename to core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_focused.png diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_hovered.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_focused.png.mcmeta similarity index 100% rename from core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_hovered.png.mcmeta rename to core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_focused.png.mcmeta diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_clicked_and_hovered.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_clicked_and_focused.png similarity index 100% rename from core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_clicked_and_hovered.png rename to core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_clicked_and_focused.png diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_clicked_and_hovered.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_clicked_and_focused.png.mcmeta similarity index 100% rename from core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_clicked_and_hovered.png.mcmeta rename to core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_clicked_and_focused.png.mcmeta diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_hovered.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_focused.png similarity index 100% rename from core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_hovered.png rename to core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_focused.png diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_hovered.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_focused.png.mcmeta similarity index 100% rename from core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_hovered.png.mcmeta rename to core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_focused.png.mcmeta diff --git a/docs/gametest.md b/docs/gametest.md index 073007245..44effe952 100644 --- a/docs/gametest.md +++ b/docs/gametest.md @@ -126,11 +126,11 @@ class InputComponentsGameTest { hover() waitForComposeIdle() - assertRenderState(TextureStates.HOVERED) + assertRenderState(TextureStates.FOCUSED) click() waitForComposeIdle() - assertRenderState(TextureStates.CLICKED_AND_HOVERED) + assertRenderState(TextureStates.CLICKED_AND_FOCUSED) } } } @@ -250,7 +250,7 @@ node("Slider") { Stateful renderers (`Checkbox`, `Switch`, `Radio.kt`'s `RadioButton`, and similar theme-driven composables) set `UINode.renderState` — a test-only hook — to the `TextureStates` key they most -recently resolved (e.g. `"hovered"`, `"clicked_and_hovered"`) just before drawing. The framework +recently resolved (e.g. `"focused"`, `"clicked_and_focused"`) just before drawing. The framework never reads it back; it exists purely so a test can assert *which visual state a component resolved to* without a pixel comparison: diff --git a/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/ComposeScreenTestContext.kt b/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/ComposeScreenTestContext.kt index 55312b708..7023aa684 100644 --- a/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/ComposeScreenTestContext.kt +++ b/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/ComposeScreenTestContext.kt @@ -51,7 +51,7 @@ class TestNodeScope( context.getInput().click(x, y, button) } - /** Moves the cursor to the center of this node's on-screen bounds, without clicking - e.g. to assert a [TextureStates.HOVERED] visual state. */ + /** Moves the cursor to the center of this node's on-screen bounds, without clicking - e.g. to assert a [TextureStates.FOCUSED] visual state. */ fun hover() { val (x, y) = centerCoords() context.getInput().setCursor(x, y) diff --git a/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/InputComponentsGameTest.kt b/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/InputComponentsGameTest.kt index 1cdad6e25..dc35d4048 100644 --- a/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/InputComponentsGameTest.kt +++ b/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/InputComponentsGameTest.kt @@ -83,15 +83,15 @@ class InputComponentsGameTest { hover() waitForComposeIdle() - assertRenderState(TextureStates.HOVERED) + assertRenderState(TextureStates.FOCUSED) click() waitForComposeIdle() - assertRenderState(TextureStates.CLICKED_AND_HOVERED) { "Expected checkbox to be both checked and hovered right after a click at its own center" } + assertRenderState(TextureStates.CLICKED_AND_FOCUSED) { "Expected checkbox to be both checked and hovered right after a click at its own center" } click() waitForComposeIdle() - assertRenderState(TextureStates.HOVERED) { "Expected checkbox to be unchecked again after a second click" } + assertRenderState(TextureStates.FOCUSED) { "Expected checkbox to be unchecked again after a second click" } } } } @@ -112,7 +112,7 @@ class InputComponentsGameTest { hover() waitForComposeIdle() - assertRenderState(TextureStates.HOVERED) + assertRenderState(TextureStates.FOCUSED) } } } @@ -165,7 +165,7 @@ class InputComponentsGameTest { hover() waitForComposeIdle() - assertRenderState(TextureStates.HOVERED) + assertRenderState(TextureStates.FOCUSED) context.getInput().holdMouse(0) waitForComposeIdle() @@ -173,7 +173,7 @@ class InputComponentsGameTest { context.getInput().releaseMouse(0) waitForComposeIdle() - assertRenderState(TextureStates.HOVERED) { "Expected slider to return to hovered after releasing the drag" } + assertRenderState(TextureStates.FOCUSED) { "Expected slider to return to hovered after releasing the drag" } } } } @@ -190,7 +190,7 @@ class InputComponentsGameTest { hover() waitForComposeIdle() - assertRenderState(TextureStates.HOVERED) + assertRenderState(TextureStates.FOCUSED) click() waitForComposeIdle() From 981ac6253de6cb7e0e219d8a0e822e67dcafe006 Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Tue, 11 Aug 2026 18:52:26 -0400 Subject: [PATCH 03/30] Remove the focus-ring overlay from ButtonCore Redundant now that focused merges into the same "focused" texture state a mouse hover uses - the theme's own hovered/focused art already shows the highlight, so the extra hand-drawn outline was just visual noise on top of it. Deletes FocusRingModifier entirely (Button was its only caller). Co-Authored-By: Claude Sonnet 5 --- .../archie/gui/composables/input/Button.kt | 6 +- .../modifiers/appearance/FocusRingModifier.kt | 55 ------------------- 2 files changed, 2 insertions(+), 59 deletions(-) delete mode 100644 core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/appearance/FocusRingModifier.kt diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt index 75e01c146..2f60c09b9 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt @@ -12,7 +12,6 @@ import net.kernelpanicsoft.archie.gui.layout.Layout import net.kernelpanicsoft.archie.gui.layout.Renderer import net.kernelpanicsoft.archie.gui.modifiers.Modifier import net.kernelpanicsoft.archie.gui.modifiers.DebugModifier -import net.kernelpanicsoft.archie.gui.modifiers.appearance.focusRing import net.kernelpanicsoft.archie.gui.modifiers.sizeIn import net.kernelpanicsoft.archie.gui.modifiers.input.focusable import net.kernelpanicsoft.archie.gui.modifiers.input.onKeyEvent @@ -116,8 +115,8 @@ fun Button( * lifecycle, and vanilla keyboard/controller focus navigation - Tab/Shift-Tab and arrow keys * (via `Screen.children()`/`nextFocusPath`) can reach and activate it (Enter/Space) exactly * like a plain `AbstractWidget`, including through controller-navigation mods such as - * Controlify. It applies no visual styling of its own beyond a default focus-ring overlay - - * everything else is left entirely to [content]. + * Controlify. It applies no visual styling of its own - `isFocused` is exposed to [content] + * so callers can render their own focus indicator (see [Button]'s themed `focused` state). * * Use [ButtonCore] when you need custom button visuals. For a standard themed button, use * [Button] instead. @@ -163,7 +162,6 @@ fun ButtonCore( event.consume(bypassSuperCall = true) } } - .focusRing(focused) } else { focused.value = false Modifier diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/appearance/FocusRingModifier.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/appearance/FocusRingModifier.kt deleted file mode 100644 index 5e6fac20c..000000000 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/appearance/FocusRingModifier.kt +++ /dev/null @@ -1,55 +0,0 @@ -package net.kernelpanicsoft.archie.gui.modifiers.appearance - -import androidx.compose.runtime.State -import androidx.compose.runtime.Stable -import net.kernelpanicsoft.archie.gui.modifiers.ContentDrawScope -import net.kernelpanicsoft.archie.gui.modifiers.DrawModifier -import net.kernelpanicsoft.archie.gui.modifiers.Modifier -import net.kernelpanicsoft.archie.gui.util.KColor -import net.kernelpanicsoft.archie.gui.util.extension.drawRectOutline - -/** - * A [DrawModifier] that outlines a composable while [focused] is `true` - the visible - * counterpart to [net.kernelpanicsoft.archie.gui.modifiers.input.focusable], giving - * keyboard/controller users the same "what's focused" feedback vanilla widgets draw for free. - * - * Drawn *after* the node's own content (unlike [BorderModifier], which draws first) so the - * ring sits on top rather than being obscured by the content it's outlining. - * - * @property color ARGB packed ring colour. - * @property thickness Ring stroke width in pixels. - */ -data class FocusRingModifier( - val focused: State, - val color: Int, - val thickness: Int, -) : Modifier.Element, DrawModifier { - - override fun mergeWith(other: FocusRingModifier): FocusRingModifier = other - - override fun ContentDrawScope.draw() { - drawContent() - if (focused.value) guiGraphics.drawRectOutline(x, y, width, height, color, thickness) - } - - override fun toString(): String = "FocusRingModifier(focused=${focused.value})" -} - -/** - * Draws a [color] outline around this composable while [focused] is `true`. - * - * @param color The ring colour. - * @param thickness The ring stroke width in pixels (default 1). - */ -@Stable -fun Modifier.focusRing(focused: State, color: KColor = KColor.YELLOW, thickness: Int = 1): Modifier = - this then FocusRingModifier(focused, color.argb, thickness) - -/** - * Draws an outline around this composable while [focused] is `true`, using a raw ARGB [color]. - * - * @param thickness The ring stroke width in pixels (default 1). - */ -@Stable -fun Modifier.focusRing(focused: State, color: Int, thickness: Int = 1): Modifier = - this then FocusRingModifier(focused, color, thickness) From b12161083819b4180db2353b3012e2b690467c73 Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Tue, 11 Aug 2026 18:58:22 -0400 Subject: [PATCH 04/30] Fix modal padding: use Surface padding, not inner-Column margin ConfirmDialog, AlertDialog, PromptDialog, and ChoiceDialog are the only four ModalScope composables in the codebase. ConfirmDialog insets its content correctly with Modifier.padding(4) on the Surface itself; the other three shared ModalDialogScaffold, which instead put Modifier.margin(4) on the inner Column. Margin only grows the parent to make room for the child - it doesn't shrink the constraints the child's own content measures against (MarginModifier has no modifyInnerConstraints override, only modifyPosition). Padding does shrink those inner constraints, so it's the correct choice for insetting content within a themed background, and it's what already made ConfirmDialog look right. Switches ModalDialogScaffold to the same pattern. Co-Authored-By: Claude Sonnet 5 --- .../archie/gui/composables/modal/DialogPrimitives.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/DialogPrimitives.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/DialogPrimitives.kt index 5c9f56c76..22d6c8de2 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/DialogPrimitives.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/DialogPrimitives.kt @@ -16,6 +16,7 @@ import net.kernelpanicsoft.archie.gui.layout.Column import net.kernelpanicsoft.archie.gui.layout.Row import net.kernelpanicsoft.archie.gui.modifiers.Modifier import net.kernelpanicsoft.archie.gui.modifiers.position.margin +import net.kernelpanicsoft.archie.gui.modifiers.position.padding import net.kernelpanicsoft.archie.gui.modifiers.sizeIn import net.kernelpanicsoft.archie.gui.modifiers.width import net.kernelpanicsoft.archie.gui.theme.LocalTheme @@ -36,8 +37,10 @@ private fun ModalDialogScaffold( body: @Composable () -> Unit, actions: @Composable () -> Unit, ) { - Surface(modifier = modifier) { - Column(modifier = Modifier.margin(4), verticalArrangement = Arrangement.spacedBy(4)) { + // Padding on the Surface itself, not margin on the inner Column, matching ConfirmDialog - + // margin only grows the parent, it doesn't shrink what content measures against. + Surface(modifier = Modifier.padding(4).then(modifier)) { + Column(verticalArrangement = Arrangement.spacedBy(4)) { Text( text = title, color = LocalTheme.current.darkTextColor, From 088ca34fe69bd527ab4e79ce7bcdbd95e1b78a5c Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Tue, 11 Aug 2026 19:33:13 -0400 Subject: [PATCH 05/30] Fix IntCoordinates/IntSize packed-Long constructor corrupting x on negative y IntCoordinates(x, y) packs both into one Long as `(x.toLong() shl 32) or y.toLong()`. y.toLong() alone sign-extends a negative y across the entire upper 32 bits - the same bits x is packed into - so ORing it in unconditionally overwrites x with all 1-bits (decodes as -1) regardless of x's real value, any time y is negative. x's own sign never corrupts y, since `shl 32` always zeroes the low 32 bits it's shifted out of, independent of sign. This is the root cause of a real, reproducible bug: Scrollable places its content at (0, -scrollPos) once scrolled - the first negative y most content in this framework ever sees - so scrolling silently corrupted every scrolled node's own x-in-Long to -1 while y kept decoding fine, visible as scrolled content appearing shifted (not just item slots - anything using absoluteCoords under the scrolled subtree). IntSize has the identical bug for a negative height, fixed the same way defensively even though sizes aren't normally negative in practice. Root-caused via a GameTest DSL diagnostic reading LayoutNode.x/y directly before/after a simulated scroll (gametest/.../LayoutComponentsGameTest.kt), run against the client with :archie-gametest-neoforge:runGametestClient - confirmed x=0 decoded as x=-1 the instant y went negative, with the child node's identity and modifier list unchanged, ruling out every other candidate (recomposition timing, RootContainer re-centering, a stray modifier) before finding the actual bit-packing bug. Kept as a permanent regression test (testScrollDoesNotCorruptContentX). Co-Authored-By: Claude Sonnet 5 --- .../archie/gui/layout/IntCoordinates.kt | 9 +++++-- .../tests/LayoutComponentsGameTest.kt | 25 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/layout/IntCoordinates.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/layout/IntCoordinates.kt index 04d912239..11325cb65 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/layout/IntCoordinates.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/layout/IntCoordinates.kt @@ -16,7 +16,11 @@ value class IntCoordinates(val pair: Long) { operator fun component1() = x operator fun component2() = y - constructor(x: Int, y: Int) : this((x.toLong() shl 32) or y.toLong()) + // y.toLong() alone sign-extends a negative y across the upper 32 bits - the ones this OR + // packs x into - clobbering x to -1 regardless of its real value. Masking to the low 32 + // bits keeps y's packed bit pattern (still decoded correctly by pair.toInt(), which only + // ever reads those same low bits) without corrupting x's half. + constructor(x: Int, y: Int) : this((x.toLong() shl 32) or (y.toLong() and 0xFFFFFFFFL)) override fun toString(): String = "($x, $y)" @@ -40,7 +44,8 @@ value class IntSize(val pair: Long) { operator fun component1() = width operator fun component2() = height - constructor(width: Int, height: Int) : this((width.toLong() shl 32) or height.toLong()) + // See IntCoordinates' identically-shaped constructor for why height must be masked here too. + constructor(width: Int, height: Int) : this((width.toLong() shl 32) or (height.toLong() and 0xFFFFFFFFL)) override fun toString(): String = "($width, $height)" } diff --git a/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/LayoutComponentsGameTest.kt b/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/LayoutComponentsGameTest.kt index 49677cc27..9e53c2584 100644 --- a/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/LayoutComponentsGameTest.kt +++ b/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/LayoutComponentsGameTest.kt @@ -97,6 +97,31 @@ class LayoutComponentsGameTest { } } + @ClientGameTest + fun ClientGameTestContext.testScrollDoesNotCorruptContentX() { + // Regression test for IntCoordinates' packed-Long constructor: a negative y sign-extends + // across the bits x is packed into, so any negatively-offset content (exactly what + // Scrollable produces once scrolled - placeAt(0, -scrollPos)) previously decoded with + // x forced to -1 no matter its real value. + val scrollState = ScrollableState() + setScreen { ScrollableProbeScreen(scrollState) } + waitForScreen { + waitForLayer(0) { + node("Scrollable") { + val xBefore = computeOnClient { node.children.first().x } + assertEquals(0, xBefore) + + scroll(y = -10.0) + waitForComposeIdle() + + val (scrollOffset, xAfter) = computeOnClient { scrollState.scrollOffset to node.children.first().x } + assertTrue(scrollOffset > 0.0) { "Expected scrolling over the Scrollable node to move scrollOffset, got $scrollOffset" } + assertEquals(0, xAfter) { "Expected the scrolled content's x to stay 0 (only y should move), got $xAfter" } + } + } + } + } + @ClientGameTest fun ClientGameTestContext.testTabPanelSwitchesActiveTabOnClick() { setScreen { TabPanelProbeScreen() } From e25f3cd2671f29ddbad453efad35836cfb79e0eb Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Tue, 11 Aug 2026 20:08:37 -0400 Subject: [PATCH 06/30] Reset vanilla focus when a modal opens or closes ComposeScreen already called Screen.setInitialFocus() every frame, but this was harmless before the focus bridge since children() was always empty (no focus target to find). Now that it returns real content, calling it every frame is actively wrong: vanilla's nextFocusPath, when something is already focused, advances to the *next* Tab-order candidate rather than re-selecting the same one - so every frame while the keyboard was the last input type would auto-cycle focus to the next focusable element, indistinguishable to vanilla from a real repeated Tab press. It also never reset when a modal opened: layerManager.top scopes children() to just the top layer already, but Screen's own getFocused() reference isn't cleared automatically, so a Tab-focused base-screen button stayed marked focused (and kept rendering its focused texture) indefinitely once a modal opened on top of it, since it's no longer in scope for the reset that setInitialFocus's own nextFocusPath search would otherwise trigger. Both are fixed together: track the top layer's identity, and only clear focus + re-run setInitialFocus when it actually changes (a modal opening or closing), instead of every frame. Ports the same fix to ComposeContainerScreen, which didn't call setInitialFocus() at all before this. Verified against a live client via :archie-gametest-neoforge:runGametestClient (19/19 passing) with a new regression test (testModalOpenResetsBaseScreenFocus) that Tab-focuses a base-screen button, opens a modal over it via mouse click (which deliberately does not touch vanilla focus - see the focus bridge commit), and asserts the base button's focused render state clears. Co-Authored-By: Claude Sonnet 5 --- .../archie/gui/ComposeContainerScreen.kt | 12 ++++++++ .../archie/gui/ComposeScreen.kt | 18 +++++++++++- .../internal/tests/ModalComponentsGameTest.kt | 28 +++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeContainerScreen.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeContainerScreen.kt index 485864a0e..c3d64f26c 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeContainerScreen.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeContainerScreen.kt @@ -16,6 +16,7 @@ import net.kernelpanicsoft.archie.gui.composables.containers.RootContainer import net.kernelpanicsoft.archie.gui.focus.collectFocusableChildren import net.kernelpanicsoft.archie.gui.item.ComposeItemContainerMenu import net.kernelpanicsoft.archie.gui.item.LocalItemState +import net.kernelpanicsoft.archie.gui.layer.Layer import net.kernelpanicsoft.archie.gui.layer.LayerStackManager import net.kernelpanicsoft.archie.gui.layer.LocalLayerManager import net.kernelpanicsoft.archie.gui.layout.IntCoordinates @@ -115,6 +116,9 @@ abstract class ComposeContainerScreen>( private var lastMouseX = 0.0 private var lastMouseY = 0.0 + /** The layer [render] last ran [setInitialFocus] for - see its use there. */ + private var lastTopLayer: Layer? = null + override fun isComposeIdle(): Boolean = !applyScheduled && !hasFrameWaiters && recomposeJob?.isActive != true @@ -249,6 +253,14 @@ abstract class ComposeContainerScreen>( { renderNodes(false, guiGraphics, mouseX, mouseY, partialTick) } + + // See ComposeScreen.renderNodes for why this only runs when the top layer actually + // changed (a modal opening or closing), not every frame. + if (layerManager.top !== lastTopLayer) { + lastTopLayer = layerManager.top + clearFocus() + setInitialFocus() + } } override fun isHovering( diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeScreen.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeScreen.kt index 2bfce04c1..8b3c795a8 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeScreen.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeScreen.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.snapshots.Snapshot import com.mojang.blaze3d.platform.InputConstants import kotlinx.coroutines.* import net.kernelpanicsoft.archie.gui.focus.collectFocusableChildren +import net.kernelpanicsoft.archie.gui.layer.Layer import net.kernelpanicsoft.archie.gui.layer.LayerStackManager import net.kernelpanicsoft.archie.gui.layer.LocalLayerManager import net.kernelpanicsoft.archie.gui.layout.* @@ -143,6 +144,9 @@ abstract class ComposeScreen( private var lastMouseX = Double.NEGATIVE_INFINITY private var lastMouseY = Double.NEGATIVE_INFINITY + /** The layer [renderNodes] last ran [setInitialFocus] for - see its use there. */ + private var lastTopLayer: Layer? = null + // `Recomposer.hasPendingWork` is Compose's own atomically-maintained "is there recomposition, // apply-changes, or effect work outstanding" signal - the same one Compose's own test tooling // (ComposeTestRule.waitForIdle()) uses. Reimplementing this by hand via applyScheduled/ @@ -223,7 +227,19 @@ abstract class ComposeScreen( hasFrameWaiters = false recomposeJob = composeScope.launch { clock.sendFrame(System.nanoTime()) } } - setInitialFocus() + + // setInitialFocus() re-runs vanilla's own Tab-navigation search (nextFocusPath) to find + // something to focus, which visits whatever's already focused first - calling it every + // frame while nothing changed would auto-advance focus to the next candidate each frame + // (indistinguishable, to vanilla, from a real Tab press) whenever the keyboard was the + // last input type. Only re-run it when the top layer actually changed - a modal opening + // or closing - and clear the old focus first, since a modal opening on top otherwise + // leaves the base screen's element (now hidden behind it) marked focused indefinitely. + if (layerManager.top !== lastTopLayer) { + lastTopLayer = layerManager.top + clearFocus() + setInitialFocus() + } } override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) { diff --git a/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/ModalComponentsGameTest.kt b/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/ModalComponentsGameTest.kt index b78e4b820..d61e6e524 100644 --- a/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/ModalComponentsGameTest.kt +++ b/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/ModalComponentsGameTest.kt @@ -12,6 +12,7 @@ import net.kernelpanicsoft.archie.gui.composables.theme.TextureStates import net.kernelpanicsoft.archie.gui.layer.LocalLayerManager import net.kernelpanicsoft.archie.gui.layout.Column import net.minecraft.network.chat.Component +import org.lwjgl.glfw.GLFW import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicReference @@ -152,6 +153,33 @@ class ModalComponentsGameTest { assertTrue(cancelled.get()) { "Expected ConfirmDialog's onCancel to fire" } } } + + @ClientGameTest + fun ClientGameTestContext.testModalOpenResetsBaseScreenFocus() { + setScreen { ModalComponentsProbeScreen() } + waitForScreen { + getInput().pressKey(GLFW.GLFW_KEY_TAB) + waitForComposeIdle() + + val triggers = baseLayer.rootNode { nodes("Button") } + triggers[0] { + assertRenderState(TextureStates.FOCUSED) { "Expected Tab to vanilla-focus the first base-screen button" } + } + + // Opening the modal is a mouse click, not Tab, so the base screen's vanilla focus + // reference is still pointing at triggers[0] the instant the modal appears - exactly + // the stale-focus scenario the reset needs to clear. + triggers[3] { click() } // "Open Confirm" + waitFor { _ -> layerCount == 2 } + waitForComposeIdle() + + triggers[0] { + assertTrue(renderState != TextureStates.FOCUSED) { + "Expected the base screen's button to lose vanilla focus once a modal opened on top of it" + } + } + } + } } private class ModalComponentsProbeScreen( From 90a6ee0796360ee7bcdf6cdfe90f569d305677e9 Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Tue, 11 Aug 2026 20:17:30 -0400 Subject: [PATCH 07/30] Fix Collapsible's separator bar filling unbounded height instead of content's Collapsible's expanded content is measured with maxHeight = Int.MAX_VALUE deliberately, to capture the content's natural height for the expand/collapse animation (visibleHeight = measured height * animation progress). The separator Spacer next to that content used fillMaxHeight(), which fills whatever maxHeight it's given - here, that unbounded Int.MAX_VALUE, not the content's actual height, ballooning both the separator and the whole Row's reported height into the billions of pixels and pushing everything after the Collapsible far off-screen. Replaces the plain Row with a small custom Layout that measures the content first, then constrains the separator to exactly that height - fillMaxHeight() then correctly fills *that* bounded constraint. Hardens the existing Collapsible GameTest with a height-sanity assertion (assertAllDescendantsSized only rejects non-positive sizes, so it never caught this). Verified against a live client via :archie-gametest-neoforge:runGametestClient (19/19 passing). Co-Authored-By: Claude Sonnet 5 --- .../gui/composables/containers/Collapsible.kt | 26 ++++++++++++++++++- .../tests/LayoutComponentsGameTest.kt | 9 +++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/Collapsible.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/Collapsible.kt index 721d8d5ff..a8111c86b 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/Collapsible.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/Collapsible.kt @@ -121,7 +121,31 @@ fun Collapsible( } }, ) { - Row(horizontalArrangement = Arrangement.spacedBy(5)) { + // A plain Row can't be used here: this whole subtree is measured with an + // unbounded maxHeight (see the measurePolicy above, which needs the content's + // natural height for the expand animation), so the separator's fillMaxHeight() + // would fill that unbounded height instead of matching its sibling - producing a + // separator (and this Row's own reported height) sized in the billions of pixels. + // Measuring content first and constraining the separator to its exact height + // sidesteps that: fillMaxHeight() then fills *this* bounded height correctly. + Layout( + name = "Row", + measurePolicy = { _, measurables, constraints -> + val (separator, box) = measurables + val boxPlaceable = box.measure(constraints.copy(minHeight = 0)) + val separatorPlaceable = separator.measure( + constraints.copy(minHeight = boxPlaceable.height, maxHeight = boxPlaceable.height) + ) + val spacing = 5 + MeasureResult( + separatorPlaceable.width + spacing + boxPlaceable.width, + maxOf(separatorPlaceable.height, boxPlaceable.height), + ) { + separatorPlaceable.placeAt(0, 0) + boxPlaceable.placeAt(separatorPlaceable.width + spacing, 0) + } + }, + ) { Spacer( modifier = Modifier .then(PaddingModifier(PaddingValues(left = 5))) diff --git a/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/LayoutComponentsGameTest.kt b/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/LayoutComponentsGameTest.kt index 9e53c2584..2ba1177bc 100644 --- a/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/LayoutComponentsGameTest.kt +++ b/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/LayoutComponentsGameTest.kt @@ -64,11 +64,20 @@ class LayoutComponentsGameTest { node("Row") { click() } waitForComposeIdle() + waitTicks(10) // let the expand animation (220ms) finish settling assertTrue(toggled.get()) { "Expected onToggled to fire on header click" } assertHasDescendant("CollapsibleContent") assertAllDescendantsSized() + // Regression check: the separator Spacer's fillMaxHeight() previously filled + // CollapsibleContent's own deliberately-unbounded measure constraint instead + // of matching its sibling content, ballooning this to ~Int.MAX_VALUE. + val contentHeight = node("CollapsibleContent") { context.computeOnClient { node.height } } + assertTrue(contentHeight in 1..30) { + "Expected CollapsibleContent's height to roughly match a single line of text, got $contentHeight" + } + node("Row") { click() } waitForComposeIdle() assertTrue(!hasDescendant("CollapsibleContent")) { "Expected content hidden again after collapsing" } From d4ce012687b3d019e8a3593606cfde40da899142 Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Wed, 12 Aug 2026 14:50:21 -0400 Subject: [PATCH 08/30] Adopt a real InteractionSource model, mirroring Compose Foundation's API shape Introduces Interaction/InteractionSource (PressInteraction, HoverInteraction, FocusInteraction, DragInteraction; MutableInteractionSource; collectIsXAsState composables) and rebuilds the input-composable stack on top of it, matching Compose Foundation's real modifier signatures wherever this framework's architecture allows: - Modifier.focusable(enabled, interactionSource) - same shape as Android's, emitting FocusInteraction. Can't be backed by a stateful Modifier.Node here (this framework's modifiers are plain immutable data), so it's @Composable and remembers its own focus flag instead - transparent to callers. - Modifier.hoverable(interactionSource, enabled) - exact signature match, no extra callbacks; observe via collectIsHoveredAsState like Android. - Modifier.pressable(interactionSource, enabled, onPress) - the press half of clickable; onPress stays because a press is inherently an action, unlike hover which is pure state. - Modifier.draggable(state, orientation, enabled, interactionSource, onDragStarted, onDragStopped) + DraggableState/rememberDraggableState(onDelta) - real delta-dispatch shape, not a raw per-event callback. - Modifier.toggleable/selectable(value/selected, enabled, interactionSource, onValueChange/onClick) - combine focusable+hoverable+pressable into one modifier applied directly to a widget's own node, the same way Android's do, instead of needing Clickable's extra wrapping container. Clickable itself now builds on focusable+hoverable+pressable rather than raw onPointerEvent calls, and gained Enter/Space activation (matching Compose Foundation's own clickable baking in keyboard activation) plus an isFocused content parameter. Checkbox, Switch, RadioButton, and Tab all migrate off Clickable onto toggleable/selectable directly - each drops the extra Box wrapper Clickable required, simplifying their node tree by one level (radio options are now Row { RadioButton, Text } instead of Row { Box { RadioButton }, Text }). Slider keeps its own press/drag handling (real Compose Foundation's own Slider doesn't build on plain draggable either, for the same reason: a click on the track needs to jump to an absolute position, which a pure delta-dispatch model can't express) but now emits DragInteraction manually and gained Left/Right arrow-key value nudging while focused. Fixes a stale test assertion (RadioGroup's Row expected child names [Box, Text], now correctly [RadioButton, Text] after the wrapper removal). Verified against a live client via :archie-gametest-neoforge:runGametestClient (19/19 passing) after two iterations - the removed Box wrapper broke one existing hierarchy assertion, now fixed to match the simpler tree. Co-Authored-By: Claude Sonnet 5 --- .../composables/containers/TabContainer.kt | 132 +++++----- .../archie/gui/composables/input/Button.kt | 31 +-- .../archie/gui/composables/input/Checkbox.kt | 71 +++--- .../archie/gui/composables/input/Clickable.kt | 81 +++--- .../archie/gui/composables/input/Radio.kt | 43 ++-- .../archie/gui/composables/input/Slider.kt | 73 ++++-- .../archie/gui/composables/input/Switch.kt | 36 ++- .../gui/focus/LayoutNodeFocusAdapter.kt | 2 +- .../archie/gui/interaction/Interaction.kt | 41 +++ .../gui/interaction/InteractionSource.kt | 84 +++++++ .../gui/modifiers/input/FocusableModifier.kt | 35 --- .../gui/modifiers/input/Interactable.kt | 237 ++++++++++++++++++ .../internal/tests/InputComponentsGameTest.kt | 8 +- 13 files changed, 622 insertions(+), 252 deletions(-) create mode 100644 core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/interaction/Interaction.kt create mode 100644 core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/interaction/InteractionSource.kt delete mode 100644 core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/input/FocusableModifier.kt create mode 100644 core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/input/Interactable.kt diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/TabContainer.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/TabContainer.kt index 1f4bc72e0..a0d84e06e 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/TabContainer.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/TabContainer.kt @@ -8,9 +8,12 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import net.kernelpanicsoft.archie.gui.composables.basic.Text import net.kernelpanicsoft.archie.gui.composables.basic.Texture -import net.kernelpanicsoft.archie.gui.composables.input.ButtonCore import net.kernelpanicsoft.archie.gui.composables.theme.TextureStates import net.kernelpanicsoft.archie.gui.composables.theme.WidgetState +import net.kernelpanicsoft.archie.gui.interaction.MutableInteractionSource +import net.kernelpanicsoft.archie.gui.interaction.collectIsFocusedAsState +import net.kernelpanicsoft.archie.gui.interaction.collectIsHoveredAsState +import net.kernelpanicsoft.archie.gui.interaction.collectIsPressedAsState import net.kernelpanicsoft.archie.gui.layout.Alignment import net.kernelpanicsoft.archie.gui.layout.Arrangement import net.kernelpanicsoft.archie.gui.layout.BoxMeasurePolicy @@ -20,6 +23,7 @@ import net.kernelpanicsoft.archie.gui.layout.Renderer import net.kernelpanicsoft.archie.gui.layout.Row import net.kernelpanicsoft.archie.gui.layout.dp import net.kernelpanicsoft.archie.gui.modifiers.Modifier +import net.kernelpanicsoft.archie.gui.modifiers.input.selectable import net.kernelpanicsoft.archie.gui.modifiers.sizeIn import net.kernelpanicsoft.archie.gui.modifiers.position.padding import net.kernelpanicsoft.archie.gui.modifiers.position.offset @@ -340,71 +344,77 @@ fun Tab( val composableTheme = theme.getComposableTheme(texture) val measurePolicy = remember { BoxMeasurePolicy(Alignment.CenterStart) } - ButtonCore( + val interactionSource = remember { MutableInteractionSource() } + val isHovered by interactionSource.collectIsHoveredAsState() + val isPressed by interactionSource.collectIsPressedAsState() + val isFocused by interactionSource.collectIsFocusedAsState() + + val selectableModifier = Modifier.selectable( + selected = selected, + enabled = enabled, + interactionSource = interactionSource, onClick = { onClick(spec) }, + ) + + val stateKey = WidgetState.resolve( + composableTheme, variant, + WidgetState.clicked(selected || isPressed), WidgetState.focused(isHovered || isFocused), enabled = enabled, - modifier = modifier, - ) { isHovered, isPressed, _ -> - val stateKey = WidgetState.resolve( - composableTheme, variant, - WidgetState.clicked(selected || isPressed), WidgetState.focused(isHovered), - enabled = enabled, - ) - val state = composableTheme.getState(stateKey, variant) - val offsetModifier = Modifier - .zIndex(if (selected && elevateSelected) 1f else 0f) - .offset(x = 0, y = if (selected && !elevateSelected) -SELECTED_ELEVATION_PX else 0) - .padding(horizontal = 10, vertical = 6) - val sizeModifier = if (!composableTheme.isNineslice) { - val defaultState = composableTheme.states[TextureStates.DEFAULT] as SimpleThemeState - Modifier.sizeIn(minWidth = defaultState.width, minHeight = defaultState.height) - } else Modifier - - Layout( - name = "Tab", - measurePolicy = measurePolicy, - renderer = object : Renderer { - override fun render( - node: UINode, - x: Int, - y: Int, - guiGraphics: GuiGraphics, - mouseX: Int, - mouseY: Int, - partialTick: Float, - ) = guiGraphics { - node.renderState = stateKey - drawThemeState(state, x, y, node.width, node.height) - } - }, - modifier = sizeModifier.then(offsetModifier), + ) + val state = composableTheme.getState(stateKey, variant) + val offsetModifier = Modifier + .zIndex(if (selected && elevateSelected) 1f else 0f) + .offset(x = 0, y = if (selected && !elevateSelected) -SELECTED_ELEVATION_PX else 0) + .padding(horizontal = 10, vertical = 6) + val sizeModifier = if (!composableTheme.isNineslice) { + val defaultState = composableTheme.states[TextureStates.DEFAULT] as SimpleThemeState + Modifier.sizeIn(minWidth = defaultState.width, minHeight = defaultState.height) + } else Modifier + + Layout( + name = "Tab", + measurePolicy = measurePolicy, + renderer = object : Renderer { + override fun render( + node: UINode, + x: Int, + y: Int, + guiGraphics: GuiGraphics, + mouseX: Int, + mouseY: Int, + partialTick: Float, + ) = guiGraphics { + node.renderState = stateKey + drawThemeState(state, x, y, node.width, node.height) + } + }, + modifier = selectableModifier.then(sizeModifier).then(offsetModifier).then(modifier), + ) { + Row( + modifier = Modifier, + horizontalArrangement = Arrangement.spacedBy(iconSpacing.dp), + verticalAlignment = Alignment.CenterVertically, ) { - Row( - modifier = Modifier, - horizontalArrangement = Arrangement.spacedBy(iconSpacing.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - spec.icon?.let { icon -> - Texture( - loc = icon.texture, - uOffset = icon.uOffset, - vOffset = icon.vOffset, - u = icon.regionWidth, - v = icon.regionHeight, - textureWidth = icon.textureWidth, - textureHeight = icon.textureHeight, - modifier = Modifier.sizeIn( - minWidth = icon.displayWidth, - minHeight = icon.displayHeight, - ), - ) - } - Text( - text = spec.title, - color = if (selected) theme.darkTextColor else theme.lightTextColor, - dropShadow = !selected + spec.icon?.let { icon -> + Texture( + loc = icon.texture, + uOffset = icon.uOffset, + vOffset = icon.vOffset, + u = icon.regionWidth, + v = icon.regionHeight, + textureWidth = icon.textureWidth, + textureHeight = icon.textureHeight, + modifier = Modifier.sizeIn( + minWidth = icon.displayWidth, + minHeight = icon.displayHeight, + ), ) } + Text( + text = spec.title, + color = if (selected) theme.darkTextColor else theme.lightTextColor, + dropShadow = !selected + ) } } } diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt index 2f60c09b9..3184572ad 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt @@ -13,8 +13,6 @@ import net.kernelpanicsoft.archie.gui.layout.Renderer import net.kernelpanicsoft.archie.gui.modifiers.Modifier import net.kernelpanicsoft.archie.gui.modifiers.DebugModifier import net.kernelpanicsoft.archie.gui.modifiers.sizeIn -import net.kernelpanicsoft.archie.gui.modifiers.input.focusable -import net.kernelpanicsoft.archie.gui.modifiers.input.onKeyEvent import net.kernelpanicsoft.archie.gui.modifiers.position.offset import net.kernelpanicsoft.archie.gui.nodes.UINode import net.kernelpanicsoft.archie.gui.theme.LocalTheme @@ -23,12 +21,8 @@ import net.kernelpanicsoft.archie.gui.theme.ThemeVariants import net.kernelpanicsoft.archie.gui.util.extension.drawThemeState import net.kernelpanicsoft.archie.gui.util.extension.invoke import net.minecraft.client.gui.GuiGraphics -import org.lwjgl.glfw.GLFW import kotlin.time.Duration.Companion.milliseconds -/** GLFW key codes that activate a focused button, mirroring vanilla `AbstractWidget` activation. */ -private val BUTTON_ACTIVATION_KEYS = intArrayOf(GLFW.GLFW_KEY_ENTER, GLFW.GLFW_KEY_KP_ENTER, GLFW.GLFW_KEY_SPACE) - /** * A standard themed, clickable button. * @@ -148,33 +142,12 @@ fun ButtonCore( enabled: Boolean = true, content: @Composable (isHovered: Boolean, isPressed: Boolean, isFocused: Boolean) -> Unit, ) { - val focused = remember { mutableStateOf(false) } - - // Only a participating widget shows up in ComposeScreen.children() (see - // collectFocusableChildren) at all - mirrors AbstractWidget.nextFocusPath returning null - // while `!active`, which keeps a disabled vanilla widget out of Tab order the same way. - val focusModifier = if (enabled) { - Modifier - .focusable(focused) - .onKeyEvent { node, event -> - if (focused.value && event.keyCode in BUTTON_ACTIVATION_KEYS) { - onClick(node) - event.consume(bypassSuperCall = true) - } - } - } else { - focused.value = false - Modifier - } - Clickable( onClick = onClick, enabled = enabled, modifier = Modifier .then(DebugModifier(strs = listOf("Enabled: $enabled"))) - .then(focusModifier) .then(modifier), - ) { isHovered, isPressed -> - content(isHovered, isPressed, focused.value) - } + content = content, + ) } diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Checkbox.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Checkbox.kt index 3d0e31435..518197973 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Checkbox.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Checkbox.kt @@ -3,15 +3,15 @@ package net.kernelpanicsoft.archie.gui.composables.input import androidx.compose.runtime.* import net.kernelpanicsoft.archie.gui.composables.theme.TextureStates import net.kernelpanicsoft.archie.gui.composables.theme.WidgetState +import net.kernelpanicsoft.archie.gui.interaction.MutableInteractionSource +import net.kernelpanicsoft.archie.gui.interaction.collectIsFocusedAsState +import net.kernelpanicsoft.archie.gui.interaction.collectIsHoveredAsState import net.kernelpanicsoft.archie.gui.layout.Alignment -import net.kernelpanicsoft.archie.gui.layout.Box import net.kernelpanicsoft.archie.gui.layout.BoxMeasurePolicy import net.kernelpanicsoft.archie.gui.layout.Layout import net.kernelpanicsoft.archie.gui.layout.Renderer import net.kernelpanicsoft.archie.gui.modifiers.Modifier -import net.kernelpanicsoft.archie.gui.modifiers.debug -import net.kernelpanicsoft.archie.gui.modifiers.input.PointerEventType -import net.kernelpanicsoft.archie.gui.modifiers.input.onPointerEvent +import net.kernelpanicsoft.archie.gui.modifiers.input.toggleable import net.kernelpanicsoft.archie.gui.modifiers.sizeIn import net.kernelpanicsoft.archie.gui.nodes.UINode import net.kernelpanicsoft.archie.gui.theme.LocalTheme @@ -30,6 +30,8 @@ import net.minecraft.client.gui.GuiGraphics * * @param checked The current checked state. * @param modifier Additional modifiers applied to the outer container. + * @param enabled When `false`, the disabled state is drawn, pointer/activation-key + * events are ignored, and this drops out of the vanilla focus graph entirely. * @param texture The themed texture key to look up via [LocalTheme]. * @param variant The theme variant of [texture] to use. See [ThemeVariants]. * @param onCheckedChange Called with the new checked value when the user clicks. @@ -38,6 +40,7 @@ import net.minecraft.client.gui.GuiGraphics fun Checkbox( checked: Boolean = false, modifier: Modifier = Modifier, + enabled: Boolean = true, texture: String = "checkbox", variant: String = ThemeVariants.DEFAULT, onCheckedChange: (Boolean) -> Unit, @@ -54,10 +57,10 @@ fun Checkbox( } else Modifier CheckboxCore( - checked, - sizeModifier.then(modifier), - onCheckedChange - ) { isHovered -> + checked = checked, + enabled = enabled, + onCheckedChange = onCheckedChange, + ) { checkboxModifier, isHovered, isFocused -> Layout( name = "Checkbox", measurePolicy = BoxMeasurePolicy(Alignment.Center), @@ -74,7 +77,8 @@ fun Checkbox( ) = guiGraphics { val stateKey = WidgetState.resolve( composableTheme, variant, - WidgetState.clicked(checked), WidgetState.focused(isHovered), + WidgetState.clicked(checked), WidgetState.focused(isHovered || isFocused), + enabled = enabled, ) node.renderState = stateKey val state = composableTheme.getState(stateKey, variant) @@ -82,48 +86,53 @@ fun Checkbox( drawThemeState(state, x, y, node.width, node.height) } }, - modifier = sizeModifier + modifier = checkboxModifier.then(sizeModifier).then(modifier) ) } } /** - * A stateless, unstyled toggle composable. + * A stateless, unstyled toggle composable, built on [Modifier.toggleable]. * - * `CheckboxCore` manages hover state internally and exposes it to [content]. All visual - * styling (textures, colours, checked indicator) is the responsibility of [content]. Use - * this as the base for custom or theme-driven checkbox implementations. + * `CheckboxCore` manages hover/focus state internally and hands [content] the [Modifier] it + * needs to apply to its own node to participate in pointer/focus input - it's a vanilla + * keyboard/controller focus-navigation stop that toggles on Enter/Space while focused, the + * same as a mouse click. All visual styling (textures, colours, checked indicator) is the + * responsibility of [content]. Use this as the base for custom or theme-driven checkbox + * implementations. * * ### Example * ```kotlin * var checked by remember { mutableStateOf(false) } - * CheckboxCore(checked = checked, onCheckedChange = { checked = it }) { isHovered -> - * Box(modifier = Modifier.size(16, 16).background(if (checked) KColor.GREEN else KColor.GRAY)) + * CheckboxCore(checked = checked, onCheckedChange = { checked = it }) { modifier, isHovered, isFocused -> + * Box(modifier = modifier.size(16, 16).background(if (checked) KColor.GREEN else KColor.GRAY)) * } * ``` * * @param checked The current checked state. - * @param modifier Additional modifiers applied to the outer [Box]. + * @param enabled When `false`, pointer and activation-key events are ignored and this + * drops out of the vanilla focus graph entirely. * @param onCheckedChange Called with the new checked value when the user clicks. - * @param content The visual content; receives `isHovered` for styling. + * @param content The visual content; receives the [Modifier] to apply to its own node, + * plus `isHovered`/`isFocused` for styling. */ @Composable fun CheckboxCore( checked: Boolean = false, - modifier: Modifier = Modifier, + enabled: Boolean = true, onCheckedChange: (Boolean) -> Unit, - content: @Composable (isHovered: Boolean) -> Unit, + content: @Composable (modifier: Modifier, isHovered: Boolean, isFocused: Boolean) -> Unit, ) { - var hovered by remember { mutableStateOf(false) } + val interactionSource = remember { MutableInteractionSource() } + val isHovered by interactionSource.collectIsHoveredAsState() + val isFocused by interactionSource.collectIsFocusedAsState() - Box( - modifier = Modifier - .debug("Hovered: $hovered") - .onPointerEvent(PointerEventType.ENTER) { _, e -> hovered = true; e.consume() } - .onPointerEvent(PointerEventType.EXIT) { _, e -> hovered = false; e.consume() } - .onPointerEvent(PointerEventType.PRESS) { _, e -> onCheckedChange(!checked); e.consume() } - .then(modifier), - ) { - content(hovered) - } + val toggleableModifier = Modifier.toggleable( + value = checked, + enabled = enabled, + interactionSource = interactionSource, + onValueChange = onCheckedChange, + ) + + content(toggleableModifier, isHovered, isFocused) } diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Clickable.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Clickable.kt index cbd76eb7e..af3b5d5ab 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Clickable.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Clickable.kt @@ -3,14 +3,18 @@ package net.kernelpanicsoft.archie.gui.composables.input import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import net.kernelpanicsoft.archie.gui.interaction.MutableInteractionSource +import net.kernelpanicsoft.archie.gui.interaction.collectIsFocusedAsState +import net.kernelpanicsoft.archie.gui.interaction.collectIsHoveredAsState +import net.kernelpanicsoft.archie.gui.interaction.collectIsPressedAsState import net.kernelpanicsoft.archie.gui.layout.Alignment import net.kernelpanicsoft.archie.gui.layout.Box import net.kernelpanicsoft.archie.gui.modifiers.Modifier -import net.kernelpanicsoft.archie.gui.modifiers.input.PointerEventType -import net.kernelpanicsoft.archie.gui.modifiers.input.onPointerEvent +import net.kernelpanicsoft.archie.gui.modifiers.input.focusable +import net.kernelpanicsoft.archie.gui.modifiers.input.hoverable +import net.kernelpanicsoft.archie.gui.modifiers.input.onKeyEvent +import net.kernelpanicsoft.archie.gui.modifiers.input.pressable import net.kernelpanicsoft.archie.gui.nodes.UINode import net.kernelpanicsoft.archie.util.minecraftClient import net.minecraft.client.Minecraft @@ -20,6 +24,9 @@ private object CursorCache { val handCursor: Long by lazy { GLFW.glfwCreateStandardCursor(GLFW.GLFW_HAND_CURSOR) } } +/** GLFW key codes that activate a focused clickable, mirroring vanilla `AbstractWidget` activation. */ +private val CLICK_ACTIVATION_KEYS = intArrayOf(GLFW.GLFW_KEY_ENTER, GLFW.GLFW_KEY_KP_ENTER, GLFW.GLFW_KEY_SPACE) + private fun setHandCursor(enabled: Boolean) { // GLFW calls must happen on the render thread; DisposableEffect callbacks run on the // recomposition dispatcher, so hop over via Minecraft's thread-safe task queue. @@ -32,15 +39,22 @@ private fun setHandCursor(enabled: Boolean) { /** * Low-level unstyled clickable container used by higher-level inputs like [ButtonCore]. * - * Tracks hover/press state and fires [onClick] on press (not release), showing the system - * hand cursor on hover when [showHandCursor] is `true`. Applies no visual styling itself - - * that is entirely up to [content]. + * Tracks hover/press state via [hoverable]/[pressable] and fires [onClick] on press (not + * release), showing the system hand cursor on hover when [showHandCursor] is `true`. Also + * registers as a vanilla keyboard/controller focus-navigation stop (see [focusable]) and fires + * [onClick] on Enter/Space while focused - the same activation model Compose Foundation's own + * `Modifier.clickable` bakes in, rather than treating focus as a separate concern from click. + * Applies no visual styling itself - that is entirely up to [content]. * - * @param onClick Invoked with the receiving [UINode] on press. - * @param modifier Additional modifiers applied to the outer [Box]. - * @param enabled When `false`, pointer events are ignored and no cursor change occurs. - * @param showHandCursor Whether to switch to the hand cursor while hovered. - * @param content The visual content; receives `isHovered`/`isPressed` for styling. + * @param onClick Invoked with the receiving [UINode] on press, or on Enter/Space + * while vanilla-focused. + * @param modifier Additional modifiers applied to the outer [Box]. + * @param enabled When `false`, pointer and activation-key events are ignored, no + * cursor change occurs, and this drops out of the vanilla focus graph entirely. + * @param showHandCursor Whether to switch to the hand cursor while hovered. + * @param interactionSource Backs `isHovered`/`isPressed`/`isFocused`, collectible independently + * via `net.kernelpanicsoft.archie.gui.interaction.collectIsXAsState` too. + * @param content The visual content; receives `isHovered`/`isPressed`/`isFocused` for styling. */ @Composable fun Clickable( @@ -48,13 +62,15 @@ fun Clickable( modifier: Modifier = Modifier, enabled: Boolean = true, showHandCursor: Boolean = true, - content: @Composable (isHovered: Boolean, isPressed: Boolean) -> Unit, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + content: @Composable (isHovered: Boolean, isPressed: Boolean, isFocused: Boolean) -> Unit, ) { - var hovered by remember { mutableStateOf(false) } - var pressed by remember { mutableStateOf(false) } + val isHovered by interactionSource.collectIsHoveredAsState() + val isPressed by interactionSource.collectIsPressedAsState() + val isFocused by interactionSource.collectIsFocusedAsState() - DisposableEffect(enabled, hovered, showHandCursor) { - if ((!enabled || !hovered) && showHandCursor) setHandCursor(false) + DisposableEffect(enabled, isHovered, showHandCursor) { + if (showHandCursor) setHandCursor(enabled && isHovered) onDispose { if (showHandCursor) setHandCursor(false) } @@ -62,31 +78,18 @@ fun Clickable( Box( modifier = Modifier - .onPointerEvent(PointerEventType.ENTER) { _, e -> - if (!enabled) return@onPointerEvent - hovered = true - if (showHandCursor) setHandCursor(true) - e.consume() - } - .onPointerEvent(PointerEventType.EXIT) { _, e -> - hovered = false - pressed = false - if (showHandCursor) setHandCursor(false) - if (enabled) e.consume() - } - .onPointerEvent(PointerEventType.PRESS) { node, e -> - if (!enabled) return@onPointerEvent - pressed = true - onClick(node) - e.consume(true) - } - .onPointerEvent(PointerEventType.GLOBAL_RELEASE) { _, _ -> - pressed = false + .focusable(enabled = enabled, interactionSource = interactionSource) + .onKeyEvent { node, e -> + if (enabled && isFocused && e.keyCode in CLICK_ACTIVATION_KEYS) { + onClick(node) + e.consume(bypassSuperCall = true) + } } + .hoverable(interactionSource, enabled = enabled) + .pressable(interactionSource, enabled = enabled, onPress = onClick) .then(modifier), contentAlignment = Alignment.Center, ) { - content(hovered, pressed) + content(isHovered, isPressed, isFocused) } } - diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Radio.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Radio.kt index 9091f59db..2f421b6fc 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Radio.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Radio.kt @@ -1,10 +1,14 @@ package net.kernelpanicsoft.archie.gui.composables.input import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import net.kernelpanicsoft.archie.gui.composables.basic.Text import net.kernelpanicsoft.archie.gui.composables.theme.TextureStates import net.kernelpanicsoft.archie.gui.composables.theme.WidgetState +import net.kernelpanicsoft.archie.gui.interaction.MutableInteractionSource +import net.kernelpanicsoft.archie.gui.interaction.collectIsFocusedAsState +import net.kernelpanicsoft.archie.gui.interaction.collectIsHoveredAsState import net.kernelpanicsoft.archie.gui.layout.Alignment import net.kernelpanicsoft.archie.gui.layout.Arrangement import net.kernelpanicsoft.archie.gui.layout.BoxMeasurePolicy @@ -13,6 +17,7 @@ import net.kernelpanicsoft.archie.gui.layout.Layout import net.kernelpanicsoft.archie.gui.layout.Renderer import net.kernelpanicsoft.archie.gui.layout.Row import net.kernelpanicsoft.archie.gui.modifiers.Modifier +import net.kernelpanicsoft.archie.gui.modifiers.input.selectable import net.kernelpanicsoft.archie.gui.modifiers.sizeIn import net.kernelpanicsoft.archie.gui.nodes.UINode import net.kernelpanicsoft.archie.gui.theme.LocalTheme @@ -24,33 +29,38 @@ import net.minecraft.client.gui.GuiGraphics import net.minecraft.network.chat.Component /** - * Low-level unstyled radio-button behavior, built on [Clickable]. + * Low-level unstyled radio-button behavior, built on [Modifier.selectable]. * * Calls [onSelect] on press only when not already [selected] (clicking an already-selected - * radio option is a no-op, matching standard radio-group semantics). Applies no visuals - - * that is up to [content]. + * radio option is a no-op, matching standard radio-group semantics) - the same on Enter/Space + * while this is a vanilla keyboard/controller focus-navigation stop. Applies no visuals - that + * is up to [content]. * * @param selected Whether this option is currently selected. * @param onSelect Invoked when this (unselected) option is clicked. - * @param modifier Additional modifiers applied to the outer clickable container. * @param enabled When `false`, pointer events are ignored. - * @param content The visual content; receives hover/press state and [selected]. + * @param content The visual content; receives the [Modifier] to apply to its own node, plus + * hover/focus state and [selected]. */ @Composable fun RadioButtonCore( selected: Boolean, onSelect: () -> Unit, - modifier: Modifier = Modifier, enabled: Boolean = true, - content: @Composable (isHovered: Boolean, isPressed: Boolean, selected: Boolean) -> Unit, + content: @Composable (modifier: Modifier, isHovered: Boolean, isFocused: Boolean, selected: Boolean) -> Unit, ) { - Clickable( - onClick = { if (!selected) onSelect() }, + val interactionSource = remember { MutableInteractionSource() } + val isHovered by interactionSource.collectIsHoveredAsState() + val isFocused by interactionSource.collectIsFocusedAsState() + + val selectableModifier = Modifier.selectable( + selected = selected, enabled = enabled, - modifier = modifier, - ) { hovered, pressed -> - content(hovered, pressed, selected) - } + interactionSource = interactionSource, + onClick = { if (!selected) onSelect() }, + ) + + content(selectableModifier, isHovered, isFocused, selected) } /** @@ -88,12 +98,11 @@ fun RadioButton( selected = selected, onSelect = onSelect, enabled = enabled, - modifier = sizeModifier.then(modifier), - ) { hovered, _, currentSelected -> + ) { radioModifier, hovered, focused, currentSelected -> Layout( name = "RadioButton", measurePolicy = measurePolicy, - modifier = sizeModifier, + modifier = radioModifier.then(sizeModifier).then(modifier), renderer = object : Renderer { override fun render( node: UINode, @@ -106,7 +115,7 @@ fun RadioButton( ) = guiGraphics { val stateKey = WidgetState.resolve( composableTheme, variant, - WidgetState.clicked(currentSelected), WidgetState.focused(hovered), + WidgetState.clicked(currentSelected), WidgetState.focused(hovered || focused), enabled = enabled, ) node.renderState = stateKey diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt index a1ff330e0..21645d141 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt @@ -5,13 +5,20 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import net.kernelpanicsoft.archie.gui.interaction.DragInteraction +import net.kernelpanicsoft.archie.gui.interaction.MutableInteractionSource +import net.kernelpanicsoft.archie.gui.interaction.collectIsFocusedAsState +import net.kernelpanicsoft.archie.gui.interaction.collectIsHoveredAsState import net.kernelpanicsoft.archie.gui.layout.Alignment import net.kernelpanicsoft.archie.gui.layout.BoxMeasurePolicy import net.kernelpanicsoft.archie.gui.layout.Layout import net.kernelpanicsoft.archie.gui.layout.Renderer import net.kernelpanicsoft.archie.gui.modifiers.Modifier import net.kernelpanicsoft.archie.gui.modifiers.input.PointerEventType +import net.kernelpanicsoft.archie.gui.modifiers.input.focusable +import net.kernelpanicsoft.archie.gui.modifiers.input.hoverable import net.kernelpanicsoft.archie.gui.modifiers.input.onDrag +import net.kernelpanicsoft.archie.gui.modifiers.input.onKeyEvent import net.kernelpanicsoft.archie.gui.modifiers.input.onPointerEvent import net.kernelpanicsoft.archie.gui.modifiers.sizeIn import net.kernelpanicsoft.archie.gui.nodes.UINode @@ -22,6 +29,7 @@ import net.kernelpanicsoft.archie.gui.theme.ThemeVariants import net.kernelpanicsoft.archie.gui.util.extension.drawThemeState import net.kernelpanicsoft.archie.gui.util.extension.invoke import net.minecraft.client.gui.GuiGraphics +import org.lwjgl.glfw.GLFW import kotlin.math.roundToInt private const val SLIDER_MIN_WIDTH = 96 @@ -30,6 +38,9 @@ private const val SLIDER_THUMB_WIDTH = 8 private const val SLIDER_THUMB_HEIGHT = 20 private const val SLIDER_TRACK_HEIGHT = 2 +/** Fraction of the full 0f..1f range one arrow-key press moves, for a continuous (steps == 0) slider. */ +private const val KEYBOARD_STEP_FALLBACK = 0.05f + /** Clamps a slider value into the normalized `0f..1f` range. */ internal fun normalizeSliderValue(value: Float): Float = value.coerceIn(0f, 1f) @@ -48,20 +59,26 @@ internal fun resolveSliderThumbX(rawThumbX: Int, sliderX: Int, sliderWidth: Int, return rawThumbX.coerceIn(minThumbX, maxThumbX) } -private fun resolveSliderStateName(theme: ComposableTheme, variant: String, enabled: Boolean, hovered: Boolean, dragging: Boolean): String = - WidgetState.resolve(theme, variant, WidgetState.clicked(dragging), WidgetState.focused(hovered), enabled = enabled) +private fun resolveSliderStateName(theme: ComposableTheme, variant: String, enabled: Boolean, hovered: Boolean, dragging: Boolean, focused: Boolean): String = + WidgetState.resolve(theme, variant, WidgetState.clicked(dragging), WidgetState.focused(hovered || focused), enabled = enabled) /** - * Low-level unstyled slider behavior: drag/click-to-position and hover/drag state tracking, + * Low-level unstyled slider behavior: drag/click-to-position, hover/drag/focus state tracking, * with no visuals of its own. * + * A vanilla keyboard/controller focus-navigation stop - unlike the simple toggle inputs + * ([Checkbox], [Switch], [RadioButton]), a slider has no single "activate" action, so instead + * of [Clickable]'s Enter/Space it responds to Left/Right arrow keys while focused, nudging the + * value by one [steps] increment (or [KEYBOARD_STEP_FALLBACK] when continuous). + * * @param value The current value, normalized/snapped via [snapSliderValue]. - * @param onValueChange Called with the new normalized value on every drag/click update. + * @param onValueChange Called with the new normalized value on every drag/click/arrow-key update. * @param modifier Additional modifiers applied to the outer container. - * @param enabled When `false`, pointer events are ignored. + * @param enabled When `false`, pointer and arrow-key events are ignored and this + * drops out of the vanilla focus graph entirely. * @param steps Number of discrete increments to snap to; `0` means continuous. - * @param onValueChangeFinished Called once when a drag interaction ends (on release). - * @param content The visual content; receives hover/drag state and the + * @param onValueChangeFinished Called once when a drag or arrow-key interaction ends. + * @param content The visual content; receives hover/drag/focus state and the * normalized, snapped value to render. */ @Composable @@ -72,12 +89,17 @@ fun SliderCore( enabled: Boolean = true, steps: Int = 0, onValueChangeFinished: () -> Unit = {}, - content: @Composable (isHovered: Boolean, isDragging: Boolean, normalizedValue: Float) -> Unit, + content: @Composable (isHovered: Boolean, isDragging: Boolean, isFocused: Boolean, normalizedValue: Float) -> Unit, ) { val normalizedValue = snapSliderValue(value, steps) - var hovered by remember { mutableStateOf(false) } + // `dragging` stays a plain local var (rather than collectIsDraggedAsState()) since onDrag + // below needs to synchronously tell "a drag that started on this slider" apart from a + // stray onDrag call, not just report state for rendering. var dragging by remember { mutableStateOf(false) } + val interactionSource = remember { MutableInteractionSource() } + val hovered by interactionSource.collectIsHoveredAsState() + val isFocused by interactionSource.collectIsFocusedAsState() fun updateFromPointer(node: UINode, mouseX: Double) { val localX = (mouseX - node.x).toFloat() @@ -89,19 +111,27 @@ fun SliderCore( name = "SliderCore", measurePolicy = remember { BoxMeasurePolicy(Alignment.CenterStart) }, modifier = Modifier - .onPointerEvent(PointerEventType.ENTER) { _, event -> - if (!enabled) return@onPointerEvent - hovered = true - event.consume() - } - .onPointerEvent(PointerEventType.EXIT) { _, event -> - hovered = false - dragging = false - if (enabled) event.consume() + .focusable(enabled = enabled, interactionSource = interactionSource) + .onKeyEvent { _, event -> + if (!enabled || !isFocused) return@onKeyEvent + val step = if (steps > 0) 1f / steps else KEYBOARD_STEP_FALLBACK + val delta = when (event.keyCode) { + GLFW.GLFW_KEY_LEFT -> -step + GLFW.GLFW_KEY_RIGHT -> step + else -> return@onKeyEvent + } + onValueChange(snapSliderValue(normalizedValue + delta, steps)) + onValueChangeFinished() + event.consume(bypassSuperCall = true) } + .hoverable(interactionSource, enabled = enabled) + // Not Modifier.draggable(): that reports only a delta per movement, but pressing + // anywhere on the track needs to jump straight to that absolute position - the same + // reason Compose Foundation's own Slider doesn't build on plain draggable either. .onPointerEvent(PointerEventType.PRESS) { node, event -> if (!enabled) return@onPointerEvent dragging = true + interactionSource.tryEmit(DragInteraction.Start) updateFromPointer(node, event.mouseX) event.consume(true) } @@ -113,11 +143,12 @@ fun SliderCore( .onPointerEvent(PointerEventType.GLOBAL_RELEASE) { _, _ -> if (!enabled || !dragging) return@onPointerEvent dragging = false + interactionSource.tryEmit(DragInteraction.Stop) onValueChangeFinished() } .then(modifier), ) { - content(hovered, dragging, normalizedValue) + content(hovered, dragging, isFocused, normalizedValue) } } @@ -155,7 +186,7 @@ fun Slider( steps = steps, onValueChangeFinished = onValueChangeFinished, modifier = sizeModifier.then(modifier), - ) { hovered, dragging, normalizedValue -> + ) { hovered, dragging, focused, normalizedValue -> Layout( name = "Slider", measurePolicy = measurePolicy, @@ -183,7 +214,7 @@ fun Slider( ) val thumbY = y + (node.height - SLIDER_THUMB_HEIGHT) / 2 - val stateName = resolveSliderStateName(trackTheme, variant, enabled, hovered, dragging) + val stateName = resolveSliderStateName(trackTheme, variant, enabled, hovered, dragging, focused) node.renderState = stateName val trackState = trackTheme.getState(stateName, variant) val thumbState = thumbTheme.getState(stateName, variant) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Switch.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Switch.kt index 1a8cb21a5..aac96f213 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Switch.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Switch.kt @@ -1,17 +1,22 @@ package net.kernelpanicsoft.archie.gui.composables.input import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import net.kernelpanicsoft.archie.gui.animation.AnimationSpec import net.kernelpanicsoft.archie.gui.animation.Easings import net.kernelpanicsoft.archie.gui.animation.animateInt import net.kernelpanicsoft.archie.gui.composables.theme.TextureStates import net.kernelpanicsoft.archie.gui.composables.theme.WidgetState +import net.kernelpanicsoft.archie.gui.interaction.MutableInteractionSource +import net.kernelpanicsoft.archie.gui.interaction.collectIsFocusedAsState +import net.kernelpanicsoft.archie.gui.interaction.collectIsHoveredAsState import net.kernelpanicsoft.archie.gui.layout.BoxMeasurePolicy import net.kernelpanicsoft.archie.gui.layout.Layout import net.kernelpanicsoft.archie.gui.layout.Renderer import net.kernelpanicsoft.archie.gui.layout.Alignment import net.kernelpanicsoft.archie.gui.modifiers.Modifier +import net.kernelpanicsoft.archie.gui.modifiers.input.toggleable import net.kernelpanicsoft.archie.gui.modifiers.sizeIn import net.kernelpanicsoft.archie.gui.nodes.UINode import net.kernelpanicsoft.archie.gui.theme.LocalTheme @@ -34,23 +39,29 @@ internal fun resolveSwitchThumbOffset(thumbOffset: Int, trackWidth: Int): Int { } /** - * Low-level switch primitive exposing hover/press state and checked state to custom visuals. + * Low-level switch primitive exposing hover/focus state and checked state to custom visuals. + * Built on [Modifier.toggleable], so it's a vanilla keyboard/controller focus-navigation stop + * that toggles on Enter/Space while focused, the same as a mouse click. */ @Composable fun SwitchCore( checked: Boolean, onCheckedChange: (Boolean) -> Unit, - modifier: Modifier = Modifier, enabled: Boolean = true, - content: @Composable (isHovered: Boolean, isPressed: Boolean, checked: Boolean) -> Unit, + content: @Composable (modifier: Modifier, isHovered: Boolean, isFocused: Boolean, checked: Boolean) -> Unit, ) { - Clickable( - onClick = { onCheckedChange(!checked) }, + val interactionSource = remember { MutableInteractionSource() } + val isHovered by interactionSource.collectIsHoveredAsState() + val isFocused by interactionSource.collectIsFocusedAsState() + + val toggleableModifier = Modifier.toggleable( + value = checked, enabled = enabled, - modifier = modifier, - ) { hovered, pressed -> - content(hovered, pressed, checked) - } + interactionSource = interactionSource, + onValueChange = onCheckedChange, + ) + + content(toggleableModifier, isHovered, isFocused, checked) } /** @@ -92,12 +103,11 @@ fun Switch( checked = checked, onCheckedChange = onCheckedChange, enabled = enabled, - modifier = sizeModifier.then(modifier), - ) { hovered, _, currentChecked -> + ) { switchModifier, hovered, focused, currentChecked -> Layout( name = "Switch", measurePolicy = measurePolicy, - modifier = sizeModifier, + modifier = switchModifier.then(sizeModifier).then(modifier), renderer = object : Renderer { override fun render( node: UINode, @@ -110,7 +120,7 @@ fun Switch( ) = guiGraphics { val trackStateKey = WidgetState.resolve( trackTheme, variant, - WidgetState.clicked(currentChecked), WidgetState.focused(hovered), + WidgetState.clicked(currentChecked), WidgetState.focused(hovered || focused), enabled = enabled, ) node.renderState = trackStateKey diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/focus/LayoutNodeFocusAdapter.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/focus/LayoutNodeFocusAdapter.kt index f3c920415..dce7b36fd 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/focus/LayoutNodeFocusAdapter.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/focus/LayoutNodeFocusAdapter.kt @@ -42,7 +42,7 @@ class LayoutNodeFocusAdapter(val node: LayoutNode) : GuiEventListener { override fun isFocused(): Boolean = focusable?.focused?.value == true override fun setFocused(focused: Boolean) { - focusable?.focused?.value = focused + focusable?.setFocused(focused) } // The GuiEventListener default always returns null - AbstractWidget overrides it the same diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/interaction/Interaction.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/interaction/Interaction.kt new file mode 100644 index 000000000..2d8b90366 --- /dev/null +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/interaction/Interaction.kt @@ -0,0 +1,41 @@ +package net.kernelpanicsoft.archie.gui.interaction + +/** + * A discrete input interaction a component emits about itself through its + * [MutableInteractionSource] - modeled on Jetpack Compose's `Interaction`/`InteractionSource`. + */ +sealed interface Interaction + +/** Interactions describing a pointer press. */ +sealed interface PressInteraction : Interaction { + /** The pointer went down inside the component's bounds. */ + data object Press : PressInteraction + + /** The pointer was released after a [Press]. */ + data object Release : PressInteraction + + /** The press ended without a [Release] (e.g. the pointer left the component's bounds). */ + data object Cancel : PressInteraction +} + +/** Interactions describing pointer hover. */ +sealed interface HoverInteraction : Interaction { + data object Enter : HoverInteraction + data object Exit : HoverInteraction +} + +/** + * Interactions describing vanilla keyboard/controller focus - see + * [net.kernelpanicsoft.archie.gui.modifiers.input.focusable]. + */ +sealed interface FocusInteraction : Interaction { + data object Focus : FocusInteraction + data object Unfocus : FocusInteraction +} + +/** Interactions describing a pointer drag. */ +sealed interface DragInteraction : Interaction { + data object Start : DragInteraction + data object Stop : DragInteraction + data object Cancel : DragInteraction +} diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/interaction/InteractionSource.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/interaction/InteractionSource.kt new file mode 100644 index 000000000..1414d0440 --- /dev/null +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/interaction/InteractionSource.kt @@ -0,0 +1,84 @@ +package net.kernelpanicsoft.archie.gui.interaction + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow + +/** + * A read-only stream of [Interaction]s a component emits about itself (press, hover, focus, + * drag). Several independent pieces of UI - the widget's own visuals, a shared "indication" + * effect, a test assertion - can all observe the same interaction state this way, instead of + * each hand-rolling its own separate hovered/pressed/focused booleans. + */ +interface InteractionSource { + val interactions: Flow +} + +/** An [InteractionSource] that can also emit new [Interaction]s into itself. */ +interface MutableInteractionSource : InteractionSource { + /** Emits [interaction] without suspending, dropping it if the internal buffer is full. */ + fun tryEmit(interaction: Interaction): Boolean +} + +/** Creates a new, independent [MutableInteractionSource]. */ +fun MutableInteractionSource(): MutableInteractionSource = MutableInteractionSourceImpl() + +private class MutableInteractionSourceImpl : MutableInteractionSource { + private val flow = MutableSharedFlow(extraBufferCapacity = 16) + override val interactions: Flow = flow + override fun tryEmit(interaction: Interaction): Boolean = flow.tryEmit(interaction) +} + +/** Subscribes to [InteractionSource.interactions], reducing matching interactions to a [State]. */ +@Composable +private fun InteractionSource.collectAsState(initial: T, reduce: (T, Interaction) -> T): State { + val state = remember(this) { mutableStateOf(initial) } + LaunchedEffect(this) { + interactions.collect { interaction -> state.value = reduce(state.value, interaction) } + } + return state +} + +/** `true` while a [PressInteraction.Press] is active (until its [PressInteraction.Release]/[PressInteraction.Cancel]). */ +@Composable +fun InteractionSource.collectIsPressedAsState(): State = collectAsState(false) { current, interaction -> + when (interaction) { + is PressInteraction.Press -> true + is PressInteraction.Release, is PressInteraction.Cancel -> false + else -> current + } +} + +/** `true` while the pointer is hovering the component. */ +@Composable +fun InteractionSource.collectIsHoveredAsState(): State = collectAsState(false) { current, interaction -> + when (interaction) { + is HoverInteraction.Enter -> true + is HoverInteraction.Exit -> false + else -> current + } +} + +/** `true` while the component holds vanilla keyboard/controller focus. */ +@Composable +fun InteractionSource.collectIsFocusedAsState(): State = collectAsState(false) { current, interaction -> + when (interaction) { + is FocusInteraction.Focus -> true + is FocusInteraction.Unfocus -> false + else -> current + } +} + +/** `true` while a drag is active (until its [DragInteraction.Stop]/[DragInteraction.Cancel]). */ +@Composable +fun InteractionSource.collectIsDraggedAsState(): State = collectAsState(false) { current, interaction -> + when (interaction) { + is DragInteraction.Start -> true + is DragInteraction.Stop, is DragInteraction.Cancel -> false + else -> current + } +} diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/input/FocusableModifier.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/input/FocusableModifier.kt deleted file mode 100644 index 642f5a038..000000000 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/input/FocusableModifier.kt +++ /dev/null @@ -1,35 +0,0 @@ -package net.kernelpanicsoft.archie.gui.modifiers.input - -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.Stable -import net.kernelpanicsoft.archie.gui.modifiers.Modifier - -/** - * Marks a composable as a stop in vanilla Minecraft's built-in focus-navigation graph. - * - * A node carrying this modifier is exposed as a synthetic `GuiEventListener` leaf from - * `ComposeScreen`/`ComposeContainerScreen`'s `children()` override (see - * `net.kernelpanicsoft.archie.gui.focus.LayoutNodeFocusAdapter`), so vanilla's own Tab/ - * Shift-Tab and arrow-key navigation - and anything else that walks `GuiEventListener`, e.g. - * Controlify's controller-driven `ScreenProcessor` - can reach it exactly like an ordinary - * `AbstractWidget`. - * - * @property focused Backing focus state, shared with the adapter: vanilla writes to it via - * `setFocused`, and the owning composable reads it to render a focus indicator or gate - * activation (see [onKeyEvent]). - */ -data class FocusableModifier( - val focused: MutableState, -) : Modifier.Element { - override fun mergeWith(other: FocusableModifier): FocusableModifier = other - override fun toString(): String = "FocusableModifier(focused=${focused.value})" -} - -/** - * Registers this composable as a vanilla focus-navigation stop, backed by [focused]. - * - * Combine with [onKeyEvent], gated on `focused.value`, to react to Enter/Space while - * vanilla-focused - mirroring how a plain `AbstractWidget` handles activation. - */ -@Stable -fun Modifier.focusable(focused: MutableState): Modifier = this then FocusableModifier(focused) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/input/Interactable.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/input/Interactable.kt new file mode 100644 index 000000000..9118218e8 --- /dev/null +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/input/Interactable.kt @@ -0,0 +1,237 @@ +package net.kernelpanicsoft.archie.gui.modifiers.input + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import net.kernelpanicsoft.archie.gui.interaction.DragInteraction +import net.kernelpanicsoft.archie.gui.interaction.FocusInteraction +import net.kernelpanicsoft.archie.gui.interaction.HoverInteraction +import net.kernelpanicsoft.archie.gui.interaction.MutableInteractionSource +import net.kernelpanicsoft.archie.gui.interaction.PressInteraction +import net.kernelpanicsoft.archie.gui.interaction.collectIsFocusedAsState +import net.kernelpanicsoft.archie.gui.modifiers.Modifier +import net.kernelpanicsoft.archie.gui.nodes.UINode +import org.lwjgl.glfw.GLFW + +/** GLFW key codes that activate a focused widget, mirroring vanilla `AbstractWidget` activation. */ +internal val ACTIVATION_KEYS: IntArray = intArrayOf(GLFW.GLFW_KEY_ENTER, GLFW.GLFW_KEY_KP_ENTER, GLFW.GLFW_KEY_SPACE) + +/** + * Marks a composable as a stop in vanilla Minecraft's built-in focus-navigation graph. + * + * A node carrying this modifier is exposed as a synthetic `GuiEventListener` leaf from + * `ComposeScreen`/`ComposeContainerScreen`'s `children()` override (see + * `net.kernelpanicsoft.archie.gui.focus.LayoutNodeFocusAdapter`), so vanilla's own Tab/ + * Shift-Tab and arrow-key navigation - and anything else that walks `GuiEventListener`, e.g. + * Controlify's controller-driven `ScreenProcessor` - can reach it exactly like an ordinary + * `AbstractWidget`. + * + * @property focused Backing focus state: vanilla writes to it via `setFocused`. + * @property interactionSource When set, [FocusInteraction.Focus]/[FocusInteraction.Unfocus] is + * emitted alongside every [focused] write, so callers can observe focus the same way as + * press/hover/drag (see [collectIsFocusedAsState][net.kernelpanicsoft.archie.gui.interaction.collectIsFocusedAsState]). + */ +internal data class FocusableModifier( + val focused: MutableState, + val interactionSource: MutableInteractionSource? = null, +) : Modifier.Element { + override fun mergeWith(other: FocusableModifier): FocusableModifier = other + override fun toString(): String = "FocusableModifier(focused=${focused.value})" + + /** Writes [value] to [focused] and emits the matching [FocusInteraction], if it actually changed. */ + fun setFocused(value: Boolean) { + if (focused.value == value) return + focused.value = value + interactionSource?.tryEmit(if (value) FocusInteraction.Focus else FocusInteraction.Unfocus) + } +} + +/** + * Registers this composable as a vanilla keyboard/controller focus-navigation stop - the + * `net.kernelpanicsoft.archie` equivalent of Compose Foundation's `Modifier.focusable`. + * + * Unlike Android's, this can't be backed by an internal `Modifier.Node` (this framework's + * modifiers are plain immutable data, not stateful nodes), so the focus flag is [remember]ed + * here instead - transparent to the caller, but it does mean this overload must be called from + * a `@Composable` context, same as any other modifier factory that needs to hold state. + * + * [Clickable][net.kernelpanicsoft.archie.gui.composables.input.Clickable] already applies this + * (plus Enter/Space activation) for any click-driven widget; reach for this directly only when + * building an input with a different activation model (e.g. `SliderCore`, which nudges its + * value on arrow keys instead). + * + * @param enabled When `false`, this node drops out of the focus graph entirely - + * mirrors `AbstractWidget.active` keeping a disabled vanilla widget out of Tab order. + * @param interactionSource Optional sink for [FocusInteraction.Focus]/[FocusInteraction.Unfocus]. + */ +@Composable +fun Modifier.focusable(enabled: Boolean = true, interactionSource: MutableInteractionSource? = null): Modifier { + val focused = remember { mutableStateOf(false) } + return if (enabled) { + this then FocusableModifier(focused, interactionSource) + } else { + focused.value = false + this + } +} + +/** + * Emits [HoverInteraction] as the pointer enters/exits - matches Compose Foundation's own + * `Modifier.hoverable(interactionSource, enabled)` signature exactly. Observe the result via + * [net.kernelpanicsoft.archie.gui.interaction.collectIsHoveredAsState], same as Android; this + * has no enter/exit callbacks of its own to hook into. + */ +fun Modifier.hoverable( + interactionSource: MutableInteractionSource, + enabled: Boolean = true, +): Modifier = this + .onPointerEvent(PointerEventType.ENTER) { _, e -> + if (enabled) interactionSource.tryEmit(HoverInteraction.Enter) + e.consume() + } + .onPointerEvent(PointerEventType.EXIT) { _, e -> + interactionSource.tryEmit(HoverInteraction.Exit) + if (enabled) e.consume() + } + +/** + * Emits [PressInteraction] and invokes [onPress]/[onRelease] across a press - the + * `net.kernelpanicsoft.archie` equivalent of the press-recognition half of Compose + * Foundation's `Modifier.clickable`. Observe release via + * [net.kernelpanicsoft.archie.gui.interaction.collectIsPressedAsState] rather than a callback - + * [onPress] is kept only because, unlike hover, a press is inherently an action (the click + * itself), not just state to observe. + */ +fun Modifier.pressable( + interactionSource: MutableInteractionSource, + enabled: Boolean = true, + onPress: (UINode) -> Unit, +): Modifier = this + .onPointerEvent(PointerEventType.PRESS) { node, e -> + if (!enabled) return@onPointerEvent + interactionSource.tryEmit(PressInteraction.Press) + onPress(node) + e.consume(true) + } + .onPointerEvent(PointerEventType.GLOBAL_RELEASE) { _, _ -> + interactionSource.tryEmit(PressInteraction.Release) + } + +/** Which axis a [draggable] gesture tracks - the `net.kernelpanicsoft.archie` equivalent of Compose Foundation's `Orientation`. */ +enum class Orientation { Horizontal, Vertical } + +/** + * Reports drag deltas to a single callback - the `net.kernelpanicsoft.archie` equivalent of + * Compose Foundation's `DraggableState`. Create one with [rememberDraggableState] rather than + * implementing this directly. + */ +fun interface DraggableState { + /** Called with the drag delta, in pixels along the gesture's [Orientation], for each drag update. */ + fun dispatchRawDelta(delta: Float) +} + +/** Remembers a [DraggableState] that forwards each delta to the latest [onDelta]. */ +@Composable +fun rememberDraggableState(onDelta: (Float) -> Unit): DraggableState { + val onDeltaState = rememberUpdatedState(onDelta) + return remember { DraggableState { delta -> onDeltaState.value(delta) } } +} + +/** + * Recognizes a drag gesture along [orientation] and reports each movement as a delta to + * [state] - the `net.kernelpanicsoft.archie` equivalent of Compose Foundation's + * `Modifier.draggable(state, orientation, enabled, interactionSource, onDragStarted, + * onDragStopped)` (dropping `startDragImmediately`/`reverseDirection`, which have no + * equivalent concept here). + * + * Reports only a *delta* per movement, not an absolute position - a widget whose drag also + * needs to jump to an absolute position on the initial press (e.g. `SliderCore`'s "click the + * track to jump there") needs its own press handling for that press-time jump; [state] only + * covers the continuous drag that follows, same as Compose Foundation's own `Slider` doesn't + * build on plain `Modifier.draggable` either, for the same reason. + */ +fun Modifier.draggable( + state: DraggableState, + orientation: Orientation, + enabled: Boolean = true, + interactionSource: MutableInteractionSource? = null, + onDragStarted: () -> Unit = {}, + onDragStopped: () -> Unit = {}, +): Modifier = this + .onPointerEvent(PointerEventType.PRESS) { _, e -> + if (!enabled) return@onPointerEvent + interactionSource?.tryEmit(DragInteraction.Start) + onDragStarted() + e.consume(true) + } + .onDrag { _, e -> + if (!enabled) return@onDrag + state.dispatchRawDelta((if (orientation == Orientation.Horizontal) e.dragX else e.dragY).toFloat()) + e.consume() + } + .onPointerEvent(PointerEventType.GLOBAL_RELEASE) { _, _ -> + interactionSource?.tryEmit(DragInteraction.Stop) + onDragStopped() + } + +/** + * Marks this composable as a togglable boolean control (checkbox/switch) - the + * `net.kernelpanicsoft.archie` equivalent of Compose Foundation's `Modifier.toggleable`. + * + * Combines [focusable], [hoverable], and [pressable] into one modifier applied directly to the + * widget's own node - calling [onValueChange] with `!value` on press or Enter/Space while + * focused - so a toggle control doesn't need [Clickable][net.kernelpanicsoft.archie.gui.composables.input.Clickable]'s + * extra wrapping container just to participate in focus/hover/press. + */ +@Composable +fun Modifier.toggleable( + value: Boolean, + enabled: Boolean = true, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + onValueChange: (Boolean) -> Unit, +): Modifier { + val isFocused by interactionSource.collectIsFocusedAsState() + return this + .focusable(enabled = enabled, interactionSource = interactionSource) + .onKeyEvent { _, e -> + if (enabled && isFocused && e.keyCode in ACTIVATION_KEYS) { + onValueChange(!value) + e.consume(bypassSuperCall = true) + } + } + .hoverable(interactionSource, enabled = enabled) + .pressable(interactionSource, enabled = enabled, onPress = { onValueChange(!value) }) +} + +/** + * Marks this composable as a selectable option within a mutually exclusive group (radio + * button/tab) - the `net.kernelpanicsoft.archie` equivalent of Compose Foundation's + * `Modifier.selectable`. + * + * Same shape as [toggleable], but calls [onClick] unconditionally rather than toggling a + * boolean - "clicking an already-selected option is a no-op" is the caller's own + * responsibility (e.g. `RadioButtonCore` passing `onSelect = { if (!selected) onSelect() }`), + * matching real Android's `selectable` the same way. + */ +@Composable +fun Modifier.selectable( + selected: Boolean, + enabled: Boolean = true, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + onClick: () -> Unit, +): Modifier { + val isFocused by interactionSource.collectIsFocusedAsState() + return this + .focusable(enabled = enabled, interactionSource = interactionSource) + .onKeyEvent { _, e -> + if (enabled && isFocused && e.keyCode in ACTIVATION_KEYS) { + onClick() + e.consume(bypassSuperCall = true) + } + } + .hoverable(interactionSource, enabled = enabled) + .pressable(interactionSource, enabled = enabled, onPress = { onClick() }) +} diff --git a/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/InputComponentsGameTest.kt b/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/InputComponentsGameTest.kt index dc35d4048..30abfffef 100644 --- a/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/InputComponentsGameTest.kt +++ b/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/InputComponentsGameTest.kt @@ -59,12 +59,10 @@ class InputComponentsGameTest { assertHasDescendant("ColorPicker") assertHasDescendant("Button") - // Every RadioGroup option's Row wraps exactly one RadioButton (itself inside - // the Box every Clickable-based composable renders its content in) plus its - // label, in order. + // Every RadioGroup option's Row wraps exactly one RadioButton (applying + // Modifier.selectable directly - no wrapping Box) plus its label, in order. node("Row") { - assertChildNames("Box", "Text") - node("Box") { assertHasDescendant("RadioButton") } + assertChildNames("RadioButton", "Text") } assertAllDescendantsSized() From e00c83b159a144d44a4dfdac6ea9969ecc83f1e6 Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Wed, 12 Aug 2026 15:11:27 -0400 Subject: [PATCH 09/30] Bridge TextField focus to vanilla, scroll focused nodes into view TextFieldCore now registers with vanilla focus via LocalVanillaScreen (setFocused/clearFocus) instead of only managing its own local focus state, so Tab/Shift-Tab navigation and modal focus-reset both work consistently with text fields the same way they already do for the other input components. Adds BringIntoViewParent/LocalBringIntoViewParent: Scrollable exposes its clip bounds as a bring-into-view target, and focusable() consults it on focus gain so Tab-focusing a node scrolled out of view scrolls it back into the viewport (LayoutNodeFocusAdapter.setFocused calls through to it). Covered by testFocusScrollsIntoView. Fixes a real bug this surfaced: LocalVanillaScreen (like any local provided only around ComposeScreen/ComposeContainerScreen.start()'s base-layer content) never reached a separately pushed modal layer, since every Layer's Composition is parented directly to the top-level Recomposer as a sibling, not nested inside the base layer's own composition - so a text field inside a modal (e.g. PromptDialog) threw "Screen has not been provided". LayerStackManager now takes the vanilla Screen and re-provides LocalVanillaScreen per pushed layer, the same way it already does for LocalLayerDepth. Full live GameTest suite: 21/21 passing. --- .../archie/gui/ComposeContainerScreen.kt | 2 +- .../archie/gui/ComposeScreen.kt | 17 ++++++- .../gui/composables/containers/Scrollable.kt | 32 +++++++++++++- .../input/textfield/TextFieldCore.kt | 35 ++++++++++++--- .../archie/gui/focus/BringIntoView.kt | 21 +++++++++ .../gui/focus/LayoutNodeFocusAdapter.kt | 7 ++- .../archie/gui/layer/LayerStackManager.kt | 12 ++++- .../gui/modifiers/input/Interactable.kt | 13 ++++-- .../tests/LayoutComponentsGameTest.kt | 44 +++++++++++++++++++ 9 files changed, 169 insertions(+), 14 deletions(-) create mode 100644 core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/focus/BringIntoView.kt diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeContainerScreen.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeContainerScreen.kt index c3d64f26c..a07ec57ac 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeContainerScreen.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeContainerScreen.kt @@ -147,7 +147,7 @@ abstract class ComposeContainerScreen>( */ protected fun start(content: @Composable () -> Unit) { recomposer = Recomposer(coroutineContext) - layerManager = LayerStackManager(recomposer) + layerManager = LayerStackManager(recomposer, this) AUIScopeManager.scopes += composeScope launch { recomposer.runRecomposeAndApplyChanges() } diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeScreen.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeScreen.kt index 8b3c795a8..3257891be 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeScreen.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeScreen.kt @@ -29,6 +29,21 @@ import kotlin.coroutines.CoroutineContext val LocalScreen: ProvidableCompositionLocal = compositionLocalOf { throw IllegalStateException("Screen has not been provided") } +/** + * Provides the hosting vanilla [Screen], regardless of whether it's a [ComposeScreen] or a + * [ComposeContainerScreen] - unlike [LocalScreen]/`LocalContainerScreen`, which are mutually + * exclusive depending on the screen type, this is provided by both. + * + * `Screen.setFocused`/`getFocused`/`clearFocus` are all public vanilla API (unlike + * `setInitialFocus`/`changeFocus`, which are `protected`), so composables that need to register + * or release vanilla keyboard/controller focus explicitly - e.g. a pointer-driven text field + * syncing its local focus state back to vanilla, so Tab navigation and a modal opening over it + * both stay consistent with what's actually focused - can reach them through this without + * needing a reference to the concrete screen subclass. + */ +val LocalVanillaScreen: ProvidableCompositionLocal = + compositionLocalOf { throw IllegalStateException("Screen has not been provided") } + /** * Implemented by Compose-driven screens that recompose asynchronously, so test harnesses can * poll for a settled frame (no pending or in-flight recomposition) before asserting on rendered @@ -168,7 +183,7 @@ abstract class ComposeScreen( */ protected fun start(content: @Composable () -> Unit) { recomposer = Recomposer(coroutineContext) - layerManager = LayerStackManager(recomposer) + layerManager = LayerStackManager(recomposer, this) AUIScopeManager.scopes += composeScope launch { recomposer.runRecomposeAndApplyChanges() } diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/Scrollable.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/Scrollable.kt index a343d5367..379c8a994 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/Scrollable.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/Scrollable.kt @@ -3,6 +3,8 @@ package net.kernelpanicsoft.archie.gui.composables.containers import androidx.compose.runtime.* import net.kernelpanicsoft.archie.gui.LocalSlotClipBounds import net.kernelpanicsoft.archie.gui.SlotClipSource +import net.kernelpanicsoft.archie.gui.focus.BringIntoViewParent +import net.kernelpanicsoft.archie.gui.focus.LocalBringIntoViewParent import net.kernelpanicsoft.archie.gui.layout.* import net.kernelpanicsoft.archie.gui.modifiers.Constraints import net.kernelpanicsoft.archie.gui.modifiers.Modifier @@ -112,6 +114,34 @@ fun Scrollable( ) { val clipSource = remember { SlotClipSource() } + // Lets a focused descendant (see focusable()'s vanilla-focus bridge) scroll itself into + // view - reuses clipSource's already-tracked absolute viewport bounds rather than tracking + // origin/size separately. + val bringIntoViewParent = remember(direction, state) { + BringIntoViewParent { target -> + val bounds = clipSource.bounds ?: return@BringIntoViewParent + val containerStart: Int + val containerSize: Int + val targetStart: Int + val targetSize: Int + if (direction == ScrollDirection.VERTICAL) { + containerStart = bounds.minY; containerSize = bounds.height + targetStart = target.absoluteCoords.y; targetSize = target.height + } else { + containerStart = bounds.minX; containerSize = bounds.width + targetStart = target.absoluteCoords.x; targetSize = target.width + } + val relativeStart = targetStart - containerStart + val relativeEnd = relativeStart + targetSize + val delta = when { + relativeStart < 0 -> relativeStart + relativeEnd > containerSize -> relativeEnd - containerSize + else -> 0 + } + if (delta != 0) state.scrollBy(delta.toDouble()) + } + } + val measurePolicy = remember(direction) { object : MeasurePolicy { override fun measure( @@ -154,7 +184,7 @@ fun Scrollable( } } - CompositionLocalProvider(LocalSlotClipBounds provides clipSource) { + CompositionLocalProvider(LocalSlotClipBounds provides clipSource, LocalBringIntoViewParent provides bringIntoViewParent) { Layout( name = "Scrollable", measurePolicy = measurePolicy, diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/textfield/TextFieldCore.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/textfield/TextFieldCore.kt index 93e3138fc..d3714cf74 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/textfield/TextFieldCore.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/textfield/TextFieldCore.kt @@ -2,6 +2,10 @@ package net.kernelpanicsoft.archie.gui.composables.input.textfield import androidx.compose.runtime.* import kotlinx.coroutines.delay +import net.kernelpanicsoft.archie.gui.LocalVanillaScreen +import net.kernelpanicsoft.archie.gui.focus.LayoutNodeFocusAdapter +import net.kernelpanicsoft.archie.gui.focus.LocalBringIntoViewParent +import net.kernelpanicsoft.archie.gui.interaction.MutableInteractionSource import net.kernelpanicsoft.archie.gui.layout.Layout import net.kernelpanicsoft.archie.gui.layout.LayoutNode import net.kernelpanicsoft.archie.gui.layout.MeasureResult @@ -32,8 +36,15 @@ class TextFieldState { var displayPos by mutableStateOf(0) /** Vertical scroll offset for multi-line fields, in pixels. */ var scrollY by mutableStateOf(0.0) + + // Backs isFocused as an explicit MutableState (rather than `by mutableStateOf`) so + // TextFieldCore can share this exact state with FocusableModifier - vanilla Tab/Shift-Tab + // navigation setting this text field's vanilla focus then reads back as isFocused too, + // through the same underlying state, no separate sync step needed. + internal val focusedState = mutableStateOf(false) /** Whether the field currently holds input focus. */ - var isFocused by mutableStateOf(false) + var isFocused: Boolean by focusedState + /** Whether the blinking cursor is currently visible. */ var showCursor by mutableStateOf(false) internal var lastBlink by mutableStateOf(0L) @@ -87,10 +98,18 @@ fun TextFieldCore( content: @Composable (state: TextFieldState) -> Unit, ) { val state = rememberTextFieldState() + val interactionSource = remember { MutableInteractionSource() } + val vanillaScreen = LocalVanillaScreen.current + val bringIntoViewParent = LocalBringIntoViewParent.current - // Cursor blink coroutine + // Cursor blink coroutine. Resets lastBlink/showCursor itself on (re)entering the focused + // branch rather than relying on onFocusChange's side effect for that, since vanilla Tab + // navigation focuses this field by writing state.focusedState directly (see the focusable() + // modifier below), not through onFocusChange. LaunchedEffect(state.isFocused) { if (state.isFocused) { + state.lastBlink = System.currentTimeMillis() + state.showCursor = true while (true) { val t = System.currentTimeMillis() if (t - state.lastBlink > CURSOR_BLINK_INTERVAL_MS) { state.showCursor = !state.showCursor; state.lastBlink = t } @@ -140,9 +159,15 @@ fun TextFieldCore( } }, modifier = modifier + // Bridges this field's existing pointer-driven focus into vanilla's own + // Tab/Shift-Tab focus graph, sharing state.focusedState directly (not the public + // Modifier.focusable(), which owns its own private state) - Tab can now reach a + // text field, and losing vanilla focus (e.g. a modal opening over it, per + // ComposeScreen/ComposeContainerScreen's own focus reset) blurs it the same way. + .let { if (enabled) it.then(FocusableModifier(state.focusedState, interactionSource, bringIntoViewParent)) else it } .onKeyEvent { _, event -> if (!enabled || !state.isFocused) return@onKeyEvent - if (event.keyCode == 256) { state.onFocusChange(false); event.consume(true); return@onKeyEvent } + if (event.keyCode == 256) { vanillaScreen.clearFocus(); event.consume(true); return@onKeyEvent } var handled = true val result = when { Screen.isSelectAll(event.keyCode) -> value.copy(selection = TextRange(0, value.text.length)) @@ -165,7 +190,7 @@ fun TextFieldCore( } .onPointerEvent(PointerEventType.PRESS) { node, event -> if (state.isFocused && !node.isBounded(event.mouseX.toInt(), event.mouseY.toInt())) - state.onFocusChange(false) + vanillaScreen.clearFocus() } .onPointerEvent(PointerEventType.PRESS) { node, event -> val (nX, _) = node.absoluteCoords @@ -173,7 +198,7 @@ fun TextFieldCore( if (!singleLine && event.mouseX >= scrollBarX && event.mouseX < nX + state.layoutInfo.width) { state.isDraggingScrollbar = true } else { - state.onFocusChange(true) + vanillaScreen.setFocused(LayoutNodeFocusAdapter(node)) val lX = event.mouseX - node.absoluteCoords.x - BORDER_PADDING val lY = event.mouseY - node.absoluteCoords.y - BORDER_PADDING val cur = findCursorPos(font, value.text, lX, lY, state, singleLine) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/focus/BringIntoView.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/focus/BringIntoView.kt new file mode 100644 index 000000000..b39aa219a --- /dev/null +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/focus/BringIntoView.kt @@ -0,0 +1,21 @@ +package net.kernelpanicsoft.archie.gui.focus + +import androidx.compose.runtime.ProvidableCompositionLocal +import androidx.compose.runtime.compositionLocalOf +import net.kernelpanicsoft.archie.gui.layout.LayoutNode + +/** + * Adjusts scroll position (or otherwise) so a focused descendant's bounds become visible - the + * `net.kernelpanicsoft.archie` equivalent of Compose Foundation's `BringIntoViewRequester`/ + * `BringIntoViewParent` mechanism. `Scrollable` provides one automatically; `Modifier.focusable`'s + * vanilla-focus bridge calls it whenever a descendant gains focus, so Tab-navigating to a node + * scrolled out of view brings it back into the viewport, the same as a real browser or Android + * view does. + */ +fun interface BringIntoViewParent { + /** Called with a descendant [LayoutNode] that just gained focus. */ + fun bringIntoView(node: LayoutNode) +} + +/** Provides the nearest ancestor `Scrollable`'s [BringIntoViewParent], if any. */ +val LocalBringIntoViewParent: ProvidableCompositionLocal = compositionLocalOf { null } diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/focus/LayoutNodeFocusAdapter.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/focus/LayoutNodeFocusAdapter.kt index dce7b36fd..b3e84938d 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/focus/LayoutNodeFocusAdapter.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/focus/LayoutNodeFocusAdapter.kt @@ -42,7 +42,12 @@ class LayoutNodeFocusAdapter(val node: LayoutNode) : GuiEventListener { override fun isFocused(): Boolean = focusable?.focused?.value == true override fun setFocused(focused: Boolean) { - focusable?.setFocused(focused) + val f = focusable ?: return + f.setFocused(focused) + // A redundant call while already focused (e.g. ComponentPath.Path.applyFocus calling + // this twice for one logical focus change) is harmless here - bringing an + // already-visible node into view again is a no-op. + if (focused) f.bringIntoViewParent?.bringIntoView(node) } // The GuiEventListener default always returns null - AbstractWidget overrides it the same diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/layer/LayerStackManager.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/layer/LayerStackManager.kt index ffd26cd2f..775b928d7 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/layer/LayerStackManager.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/layer/LayerStackManager.kt @@ -34,6 +34,8 @@ import net.kernelpanicsoft.archie.gui.modifiers.input.PointerEventType import net.kernelpanicsoft.archie.gui.modifiers.input.onPointerEvent import net.kernelpanicsoft.archie.gui.modifiers.position.offset import net.kernelpanicsoft.archie.gui.nodes.UINode +import net.kernelpanicsoft.archie.gui.LocalVanillaScreen +import net.minecraft.client.gui.screens.Screen import net.minecraft.network.chat.Component import java.util.* import kotlinx.coroutines.delay @@ -87,8 +89,14 @@ data class ModalTransitionSpec( * * @param parentComposition The [CompositionContext] from the host screen, required * when creating child [Composition]s for each layer. + * @param vanillaScreen The hosting vanilla [Screen], re-provided as [LocalVanillaScreen] for + * every layer this manager pushes. Each layer is its own top-level [Composition] parented + * directly to [parentComposition] rather than nested inside the base layer's, so a + * `CompositionLocalProvider` scoped to the base layer's own content (e.g. `ComposeScreen.start`) + * never reaches a later-pushed modal layer - this has to be re-supplied here instead, the same + * way [LocalLayerDepth] already is below. */ -class LayerStackManager(private val parentComposition: CompositionContext) { +class LayerStackManager(private val parentComposition: CompositionContext, private val vanillaScreen: Screen) { /** The ordered list of active layers. Layers are rendered bottom-to-top. */ val layers = mutableStateListOf() @@ -142,7 +150,7 @@ class LayerStackManager(private val parentComposition: CompositionContext) { val layerId = UUID.randomUUID() val layerDepth = layers.size val layer = Layer(id = layerId, parentComposition = parentComposition, depth = layerDepth) { - CompositionLocalProvider(LocalLayerDepth provides layerDepth) { + CompositionLocalProvider(LocalLayerDepth provides layerDepth, LocalVanillaScreen provides vanillaScreen) { layerContent { popById(layerId) } } } diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/input/Interactable.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/input/Interactable.kt index 9118218e8..59e68e40a 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/input/Interactable.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/modifiers/input/Interactable.kt @@ -6,6 +6,8 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState +import net.kernelpanicsoft.archie.gui.focus.BringIntoViewParent +import net.kernelpanicsoft.archie.gui.focus.LocalBringIntoViewParent import net.kernelpanicsoft.archie.gui.interaction.DragInteraction import net.kernelpanicsoft.archie.gui.interaction.FocusInteraction import net.kernelpanicsoft.archie.gui.interaction.HoverInteraction @@ -29,14 +31,18 @@ internal val ACTIVATION_KEYS: IntArray = intArrayOf(GLFW.GLFW_KEY_ENTER, GLFW.GL * Controlify's controller-driven `ScreenProcessor` - can reach it exactly like an ordinary * `AbstractWidget`. * - * @property focused Backing focus state: vanilla writes to it via `setFocused`. - * @property interactionSource When set, [FocusInteraction.Focus]/[FocusInteraction.Unfocus] is + * @property focused Backing focus state: vanilla writes to it via `setFocused`. + * @property interactionSource When set, [FocusInteraction.Focus]/[FocusInteraction.Unfocus] is * emitted alongside every [focused] write, so callers can observe focus the same way as * press/hover/drag (see [collectIsFocusedAsState][net.kernelpanicsoft.archie.gui.interaction.collectIsFocusedAsState]). + * @property bringIntoViewParent The nearest ancestor `Scrollable`'s [BringIntoViewParent], if + * any - called on focus gain so Tab-navigating to a scrolled-out-of-view node brings it back + * into the viewport. */ internal data class FocusableModifier( val focused: MutableState, val interactionSource: MutableInteractionSource? = null, + val bringIntoViewParent: BringIntoViewParent? = null, ) : Modifier.Element { override fun mergeWith(other: FocusableModifier): FocusableModifier = other override fun toString(): String = "FocusableModifier(focused=${focused.value})" @@ -70,8 +76,9 @@ internal data class FocusableModifier( @Composable fun Modifier.focusable(enabled: Boolean = true, interactionSource: MutableInteractionSource? = null): Modifier { val focused = remember { mutableStateOf(false) } + val bringIntoViewParent = LocalBringIntoViewParent.current return if (enabled) { - this then FocusableModifier(focused, interactionSource) + this then FocusableModifier(focused, interactionSource, bringIntoViewParent) } else { focused.value = false this diff --git a/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/LayoutComponentsGameTest.kt b/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/LayoutComponentsGameTest.kt index 2ba1177bc..21518dbde 100644 --- a/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/LayoutComponentsGameTest.kt +++ b/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/LayoutComponentsGameTest.kt @@ -14,6 +14,7 @@ import net.kernelpanicsoft.archie.gui.composables.containers.Panel import net.kernelpanicsoft.archie.gui.composables.containers.Scrollable import net.kernelpanicsoft.archie.gui.composables.containers.ScrollableState import net.kernelpanicsoft.archie.gui.composables.containers.TabPanel +import net.kernelpanicsoft.archie.gui.composables.input.Button import net.kernelpanicsoft.archie.gui.composables.theme.TextureStates import net.kernelpanicsoft.archie.gui.layout.Arrangement import net.kernelpanicsoft.archie.gui.layout.Column @@ -25,6 +26,7 @@ import net.kernelpanicsoft.archie.gui.modifiers.width import net.kernelpanicsoft.archie.gui.theme.Theme import net.minecraft.network.chat.Component import net.minecraft.resources.ResourceLocation +import org.lwjgl.glfw.GLFW import java.util.concurrent.atomic.AtomicBoolean /** @@ -131,6 +133,29 @@ class LayoutComponentsGameTest { } } + @ClientGameTest + fun ClientGameTestContext.testFocusScrollsIntoView() { + val scrollState = ScrollableState() + setScreen { FocusScrollProbeScreen(scrollState) } + waitForScreen { + waitForLayer(0) { + node("Scrollable") { + assertEquals(0.0, computeOnClient { scrollState.scrollOffset }) + + // The 80px-tall viewport only shows the first few of 20 buttons - Tab far + // enough to reach one scrolled out of view below the fold. + repeat(15) { getInput().pressKey(GLFW.GLFW_KEY_TAB) } + waitForComposeIdle() + + val offsetAfter = computeOnClient { scrollState.scrollOffset } + assertTrue(offsetAfter > 0.0) { + "Expected Tab-focusing a button scrolled out of view to scroll it into view, got offset=$offsetAfter" + } + } + } + } + } + @ClientGameTest fun ClientGameTestContext.testTabPanelSwitchesActiveTabOnClick() { setScreen { TabPanelProbeScreen() } @@ -216,6 +241,25 @@ private class ScrollableProbeScreen( } } +private class FocusScrollProbeScreen( + private val scrollState: ScrollableState, +) : ComposeScreen(Component.literal("Focus Scroll Probe")) { + override fun init() { + super.init() + start { + Theme { + Scrollable(state = scrollState, modifier = Modifier.height(80).width(120)) { + Column(verticalArrangement = Arrangement.spacedBy(2)) { + repeat(20) { index -> + Button(onClick = {}) { Text(Component.literal("Button $index"), dropShadow = false) } + } + } + } + } + } + } +} + /** A zero-size, invisible node whose mere presence in the tree marks which branch was composed. */ @Composable private fun Marker(name: String) { From e4a6df11f2ec8c598a8eec331b12be81e5cdaf6f Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Wed, 12 Aug 2026 15:16:02 -0400 Subject: [PATCH 10/30] Share all screen-scoped composition locals across every layer, not just LocalVanillaScreen e00c83b15's fix special-cased LocalVanillaScreen alone by having LayerStackManager take the vanilla Screen and re-provide just that one local per pushed layer. But the underlying problem - a CompositionLocalProvider wrapping only the base layer's own content never reaching a separately pushed layer, since every Layer is its own top-level Composition parented directly to the shared Recomposer as a sibling, not nested inside another layer's - applies identically to every other screen-scoped local (LocalScreen, LocalContainerScreen, LocalContainerMenu, LocalSlotData, LocalBlockEntityState, LocalItemState, LocalLayerManager), which happened to work only because nothing pushed as a modal/dropdown/tooltip needed them yet. LayerStackManager now takes a general `screenLocals` wrapper instead of a single Screen param, and push() applies it to every layer it creates - so ComposeScreen/ComposeContainerScreen.start() declare their full set of screen-wide locals once, and any layer pushed through this manager (base, modal, or otherwise) sees all of them automatically, without each call site needing to remember which ones matter. Full live GameTest suite: 21/21 passing. --- .../archie/gui/ComposeContainerScreen.kt | 24 ++++++++++------- .../archie/gui/ComposeScreen.kt | 20 ++++++++------ .../archie/gui/layer/LayerStackManager.kt | 27 +++++++++++-------- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeContainerScreen.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeContainerScreen.kt index a07ec57ac..6a22769d6 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeContainerScreen.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeContainerScreen.kt @@ -147,14 +147,13 @@ abstract class ComposeContainerScreen>( */ protected fun start(content: @Composable () -> Unit) { recomposer = Recomposer(coroutineContext) - layerManager = LayerStackManager(recomposer, this) - - AUIScopeManager.scopes += composeScope - launch { recomposer.runRecomposeAndApplyChanges() } - - layerManager.push { _ -> + layerManager = LayerStackManager(recomposer) { layerContent -> + // Applied to every layer this screen ever pushes (base, modal, dropdown, tooltip + // alike) - see LayerStackManager's screenLocals doc for why a plain + // CompositionLocalProvider wrapping only this start() call wouldn't reach them. CompositionLocalProvider( LocalContainerScreen provides this, + LocalVanillaScreen provides this, LocalContainerMenu provides menu, LocalSlotData provides menu.slotData, // Only one of these is non-null for any given menu - LocalBlockEntityState / @@ -164,10 +163,15 @@ abstract class ComposeContainerScreen>( LocalBlockEntityState provides (menu as? ComposeBlockContainerMenu<*, *>)?.blockEntityState, LocalItemState provides (menu as? ComposeItemContainerMenu<*>)?.itemState, LocalLayerManager provides layerManager, - ) { - RootContainer { - content() - } + ) { layerContent() } + } + + AUIScopeManager.scopes += composeScope + launch { recomposer.runRecomposeAndApplyChanges() } + + layerManager.push { _ -> + RootContainer { + content() } } } diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeScreen.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeScreen.kt index 3257891be..bc1a4f331 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeScreen.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeScreen.kt @@ -183,19 +183,23 @@ abstract class ComposeScreen( */ protected fun start(content: @Composable () -> Unit) { recomposer = Recomposer(coroutineContext) - layerManager = LayerStackManager(recomposer, this) + layerManager = LayerStackManager(recomposer) { layerContent -> + // Applied to every layer this screen ever pushes (base, modal, dropdown, tooltip + // alike) - see LayerStackManager's screenLocals doc for why a plain + // CompositionLocalProvider wrapping only this start() call wouldn't reach them. + CompositionLocalProvider( + LocalScreen provides this, + LocalVanillaScreen provides this, + LocalLayerManager provides layerManager, + ) { layerContent() } + } AUIScopeManager.scopes += composeScope launch { recomposer.runRecomposeAndApplyChanges() } layerManager.push { _ -> - CompositionLocalProvider( - LocalScreen provides this, - LocalLayerManager provides layerManager, - ) { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - content() - } + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + content() } } } diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/layer/LayerStackManager.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/layer/LayerStackManager.kt index 775b928d7..ac35981c9 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/layer/LayerStackManager.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/layer/LayerStackManager.kt @@ -34,8 +34,6 @@ import net.kernelpanicsoft.archie.gui.modifiers.input.PointerEventType import net.kernelpanicsoft.archie.gui.modifiers.input.onPointerEvent import net.kernelpanicsoft.archie.gui.modifiers.position.offset import net.kernelpanicsoft.archie.gui.nodes.UINode -import net.kernelpanicsoft.archie.gui.LocalVanillaScreen -import net.minecraft.client.gui.screens.Screen import net.minecraft.network.chat.Component import java.util.* import kotlinx.coroutines.delay @@ -89,14 +87,19 @@ data class ModalTransitionSpec( * * @param parentComposition The [CompositionContext] from the host screen, required * when creating child [Composition]s for each layer. - * @param vanillaScreen The hosting vanilla [Screen], re-provided as [LocalVanillaScreen] for - * every layer this manager pushes. Each layer is its own top-level [Composition] parented - * directly to [parentComposition] rather than nested inside the base layer's, so a - * `CompositionLocalProvider` scoped to the base layer's own content (e.g. `ComposeScreen.start`) - * never reaches a later-pushed modal layer - this has to be re-supplied here instead, the same - * way [LocalLayerDepth] already is below. + * @param screenLocals Wraps every layer's content in whatever `CompositionLocalProvider` + * the host screen needs visible screen-wide (e.g. `LocalScreen`, `LocalVanillaScreen`, + * [LocalLayerManager]). Each layer is its own top-level [Composition] + * parented directly to [parentComposition] rather than nested inside another layer's, so + * ordinary composition-local scoping - a `CompositionLocalProvider` wrapping only the base + * layer's own content, say - never reaches a later-pushed modal/dropdown/tooltip layer. + * [push] applies this to *every* layer it creates so all such locals stay implicitly shared + * across the whole stack instead of each caller needing to remember which ones to re-supply. */ -class LayerStackManager(private val parentComposition: CompositionContext, private val vanillaScreen: Screen) { +class LayerStackManager( + private val parentComposition: CompositionContext, + private val screenLocals: @Composable (content: @Composable () -> Unit) -> Unit, +) { /** The ordered list of active layers. Layers are rendered bottom-to-top. */ val layers = mutableStateListOf() @@ -150,8 +153,10 @@ class LayerStackManager(private val parentComposition: CompositionContext, priva val layerId = UUID.randomUUID() val layerDepth = layers.size val layer = Layer(id = layerId, parentComposition = parentComposition, depth = layerDepth) { - CompositionLocalProvider(LocalLayerDepth provides layerDepth, LocalVanillaScreen provides vanillaScreen) { - layerContent { popById(layerId) } + screenLocals { + CompositionLocalProvider(LocalLayerDepth provides layerDepth) { + layerContent { popById(layerId) } + } } } layers.add(layer) From f94de16623f68b2a151e94be2a708d998bc37c6d Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Wed, 12 Aug 2026 15:24:50 -0400 Subject: [PATCH 11/30] Fix undersized modal action buttons and a discarded-modifier bug AlertDialog/PromptDialog/ChoiceDialog's action buttons had no size modifier at all, unlike ConfirmDialog's Modifier.sizeIn(minWidth = 50, minHeight = 20) - since button textures are nine-slice (no stretch target implied), Button applies no size floor of its own without an explicit modifier, so these buttons shrank to fit just their Text label instead of looking like normal buttons. Gave all of them the same explicit sizeIn as ConfirmDialog, including ChoiceDialog's per-choice buttons (previously width-only, no height floor). Also fixed a real bug surfaced while investigating: Button.kt and Surface.kt's non-nineslice size-floor branch built its constraint via modifier.apply { sizeIn(...) }, but apply() returns the unmodified receiver - the computed SizeModifier was silently discarded every time. Doesn't change anything for the currently-nineslice button/ surface textures, but was completely dead for any theme that isn't nineslice. Rewritten to actually chain the result. Full live GameTest suite: 21/21 passing. --- .../gui/composables/containers/Surface.kt | 9 ++--- .../archie/gui/composables/input/Button.kt | 13 +++---- .../gui/composables/modal/DialogPrimitives.kt | 36 ++++++++++++------- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/Surface.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/Surface.kt index 44ceb8f7e..b5eeb943d 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/Surface.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/Surface.kt @@ -62,15 +62,12 @@ fun Surface( drawThemeState(state, x, y, node.width, node.height) } }, - modifier = Modifier.debug(state.texture.toString()).apply { + modifier = Modifier.debug(state.texture.toString()).let { base -> if (!composableTheme.isNineslice) { with(composableTheme.states[TextureStates.DEFAULT] as SimpleThemeState) { - sizeIn( - minWidth = width, - minHeight = height - ) + base.sizeIn(minWidth = width, minHeight = height) } - } + } else base } then modifier, content = content ) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt index 3184572ad..af1203813 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt @@ -86,16 +86,11 @@ fun Button( drawThemeState(state, x, y, node.width, node.height) } }, - modifier = modifier.apply { - if (!composableTheme.isNineslice) { - with(composableTheme.states[TextureStates.DEFAULT] as SimpleThemeState) { - sizeIn( - minWidth = width, - minHeight = height - ) - } + modifier = (if (!composableTheme.isNineslice) { + with(composableTheme.states[TextureStates.DEFAULT] as SimpleThemeState) { + modifier.sizeIn(minWidth = width, minHeight = height) } - }.offset(x = 0, y = pressOffset) + } else modifier).offset(x = 0, y = pressOffset) ) } } diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/DialogPrimitives.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/DialogPrimitives.kt index 22d6c8de2..f7eadf792 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/DialogPrimitives.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/DialogPrimitives.kt @@ -78,10 +78,13 @@ fun ModalScope.AlertDialog( Text(text = message, dropShadow = false, color = LocalTheme.current.darkTextColor) }, actions = { - Button(onClick = { - onConfirm() - dismiss() - }) { + Button( + onClick = { + onConfirm() + dismiss() + }, + modifier = Modifier.sizeIn(minWidth = 50, minHeight = 20), + ) { Text(confirmText, dropShadow = false) } }, @@ -128,10 +131,13 @@ fun ModalScope.PromptDialog( } }, actions = { - Button(onClick = { - onCancel() - dismiss() - }) { + Button( + onClick = { + onCancel() + dismiss() + }, + modifier = Modifier.sizeIn(minWidth = 50, minHeight = 20), + ) { Text(cancelText, dropShadow = false) } Button( @@ -140,6 +146,7 @@ fun ModalScope.PromptDialog( onConfirm(value) dismiss() }, + modifier = Modifier.sizeIn(minWidth = 50, minHeight = 20), ) { Text(confirmText, dropShadow = false) } @@ -179,7 +186,7 @@ fun ModalScope.ChoiceDialog( choices.forEach { choice -> Button( enabled = choice.enabled, - modifier = Modifier.width(150), + modifier = Modifier.sizeIn(minWidth = 150, maxWidth = 150, minHeight = 20), onClick = { onSelected(choice.value) dismiss() @@ -192,10 +199,13 @@ fun ModalScope.ChoiceDialog( } }, actions = { - Button(onClick = { - onCancel() - dismiss() - }) { + Button( + onClick = { + onCancel() + dismiss() + }, + modifier = Modifier.sizeIn(minWidth = 50, minHeight = 20), + ) { Text(cancelText, dropShadow = false) } }, From 9013eca8b288adacd9bb5e81212aae283ab15594 Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Wed, 12 Aug 2026 15:32:14 -0400 Subject: [PATCH 12/30] Add an explicit min_size theme property for a composable's intrinsic size Themes can now declare "min_size": { "width": ..., "height": ... } at the root, exposed as ComposableTheme.minSize, so a composable can have a sensible default minimum size independent of whether its texture is nine-slice - previously only a non-nine-slice sprite's own dimensions ever acted as an implicit floor, so a nine-slice component (like button, which can stretch to any size) had no intrinsic minimum at all unless every caller remembered to pass one explicitly. Extracted the shared logic as ComposableTheme.intrinsicSizeModifier(): minSize when the theme declares one, else the old non-nine-slice sprite-size fallback, else no floor - and wired it into every composable that previously duplicated (or, in Button/Surface's case, had a broken copy of) this pattern: Button, Surface, Checkbox, RadioButton, Tab. button.json now declares min_size: 50x20, matching what ConfirmDialog was previously hardcoding per-button via Modifier.sizeIn - removed that now-redundant boilerplate from ConfirmDialog and DialogPrimitives (AlertDialog/PromptDialog/ChoiceDialog), which get the same floor automatically now. Full live GameTest suite: 21/21 passing. --- .../gui/composables/containers/Surface.kt | 11 ++---- .../composables/containers/TabContainer.kt | 8 ++--- .../archie/gui/composables/input/Button.kt | 9 ++--- .../archie/gui/composables/input/Checkbox.kt | 13 ++----- .../archie/gui/composables/input/Radio.kt | 9 ++--- .../gui/composables/modal/ConfirmDialog.kt | 3 -- .../gui/composables/modal/DialogPrimitives.kt | 6 +--- .../archie/gui/theme/ComposableTheme.kt | 36 +++++++++++++++---- .../archie/archie_themes/java/button.json | 4 +++ 9 files changed, 45 insertions(+), 54 deletions(-) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/Surface.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/Surface.kt index b5eeb943d..9d3d03a14 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/Surface.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/Surface.kt @@ -9,11 +9,10 @@ import net.kernelpanicsoft.archie.gui.layout.Layout import net.kernelpanicsoft.archie.gui.layout.Renderer import net.kernelpanicsoft.archie.gui.modifiers.Modifier import net.kernelpanicsoft.archie.gui.modifiers.debug -import net.kernelpanicsoft.archie.gui.modifiers.sizeIn import net.kernelpanicsoft.archie.gui.nodes.UINode import net.kernelpanicsoft.archie.gui.theme.LocalTheme -import net.kernelpanicsoft.archie.gui.theme.SimpleThemeState import net.kernelpanicsoft.archie.gui.theme.ThemeVariants +import net.kernelpanicsoft.archie.gui.theme.intrinsicSizeModifier import net.kernelpanicsoft.archie.gui.util.extension.drawThemeState import net.kernelpanicsoft.archie.gui.util.extension.invoke import net.minecraft.client.gui.GuiGraphics @@ -62,13 +61,7 @@ fun Surface( drawThemeState(state, x, y, node.width, node.height) } }, - modifier = Modifier.debug(state.texture.toString()).let { base -> - if (!composableTheme.isNineslice) { - with(composableTheme.states[TextureStates.DEFAULT] as SimpleThemeState) { - base.sizeIn(minWidth = width, minHeight = height) - } - } else base - } then modifier, + modifier = Modifier.debug(state.texture.toString()).then(composableTheme.intrinsicSizeModifier()) then modifier, content = content ) } \ No newline at end of file diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/TabContainer.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/TabContainer.kt index a0d84e06e..ea9de0680 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/TabContainer.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/TabContainer.kt @@ -8,7 +8,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import net.kernelpanicsoft.archie.gui.composables.basic.Text import net.kernelpanicsoft.archie.gui.composables.basic.Texture -import net.kernelpanicsoft.archie.gui.composables.theme.TextureStates import net.kernelpanicsoft.archie.gui.composables.theme.WidgetState import net.kernelpanicsoft.archie.gui.interaction.MutableInteractionSource import net.kernelpanicsoft.archie.gui.interaction.collectIsFocusedAsState @@ -30,8 +29,8 @@ import net.kernelpanicsoft.archie.gui.modifiers.position.offset import net.kernelpanicsoft.archie.gui.modifiers.position.zIndex import net.kernelpanicsoft.archie.gui.nodes.UINode import net.kernelpanicsoft.archie.gui.theme.LocalTheme -import net.kernelpanicsoft.archie.gui.theme.SimpleThemeState import net.kernelpanicsoft.archie.gui.theme.ThemeVariants +import net.kernelpanicsoft.archie.gui.theme.intrinsicSizeModifier import net.kernelpanicsoft.archie.gui.util.extension.drawThemeState import net.kernelpanicsoft.archie.gui.util.extension.invoke import net.minecraft.network.chat.Component @@ -366,10 +365,7 @@ fun Tab( .zIndex(if (selected && elevateSelected) 1f else 0f) .offset(x = 0, y = if (selected && !elevateSelected) -SELECTED_ELEVATION_PX else 0) .padding(horizontal = 10, vertical = 6) - val sizeModifier = if (!composableTheme.isNineslice) { - val defaultState = composableTheme.states[TextureStates.DEFAULT] as SimpleThemeState - Modifier.sizeIn(minWidth = defaultState.width, minHeight = defaultState.height) - } else Modifier + val sizeModifier = composableTheme.intrinsicSizeModifier() Layout( name = "Tab", diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt index af1203813..5bb52fe7d 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt @@ -12,12 +12,11 @@ import net.kernelpanicsoft.archie.gui.layout.Layout import net.kernelpanicsoft.archie.gui.layout.Renderer import net.kernelpanicsoft.archie.gui.modifiers.Modifier import net.kernelpanicsoft.archie.gui.modifiers.DebugModifier -import net.kernelpanicsoft.archie.gui.modifiers.sizeIn import net.kernelpanicsoft.archie.gui.modifiers.position.offset import net.kernelpanicsoft.archie.gui.nodes.UINode import net.kernelpanicsoft.archie.gui.theme.LocalTheme -import net.kernelpanicsoft.archie.gui.theme.SimpleThemeState import net.kernelpanicsoft.archie.gui.theme.ThemeVariants +import net.kernelpanicsoft.archie.gui.theme.intrinsicSizeModifier import net.kernelpanicsoft.archie.gui.util.extension.drawThemeState import net.kernelpanicsoft.archie.gui.util.extension.invoke import net.minecraft.client.gui.GuiGraphics @@ -86,11 +85,7 @@ fun Button( drawThemeState(state, x, y, node.width, node.height) } }, - modifier = (if (!composableTheme.isNineslice) { - with(composableTheme.states[TextureStates.DEFAULT] as SimpleThemeState) { - modifier.sizeIn(minWidth = width, minHeight = height) - } - } else modifier).offset(x = 0, y = pressOffset) + modifier = modifier.then(composableTheme.intrinsicSizeModifier()).offset(x = 0, y = pressOffset) ) } } diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Checkbox.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Checkbox.kt index 518197973..6feba74cf 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Checkbox.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Checkbox.kt @@ -1,7 +1,6 @@ package net.kernelpanicsoft.archie.gui.composables.input import androidx.compose.runtime.* -import net.kernelpanicsoft.archie.gui.composables.theme.TextureStates import net.kernelpanicsoft.archie.gui.composables.theme.WidgetState import net.kernelpanicsoft.archie.gui.interaction.MutableInteractionSource import net.kernelpanicsoft.archie.gui.interaction.collectIsFocusedAsState @@ -12,11 +11,10 @@ import net.kernelpanicsoft.archie.gui.layout.Layout import net.kernelpanicsoft.archie.gui.layout.Renderer import net.kernelpanicsoft.archie.gui.modifiers.Modifier import net.kernelpanicsoft.archie.gui.modifiers.input.toggleable -import net.kernelpanicsoft.archie.gui.modifiers.sizeIn import net.kernelpanicsoft.archie.gui.nodes.UINode import net.kernelpanicsoft.archie.gui.theme.LocalTheme -import net.kernelpanicsoft.archie.gui.theme.SimpleThemeState import net.kernelpanicsoft.archie.gui.theme.ThemeVariants +import net.kernelpanicsoft.archie.gui.theme.intrinsicSizeModifier import net.kernelpanicsoft.archie.gui.util.extension.drawThemeState import net.kernelpanicsoft.archie.gui.util.extension.invoke import net.minecraft.client.gui.GuiGraphics @@ -47,14 +45,7 @@ fun Checkbox( ) { val theme = LocalTheme.current val composableTheme = theme.getComposableTheme(texture) - val sizeModifier = if (!composableTheme.isNineslice) { - with(composableTheme.states[TextureStates.DEFAULT] as SimpleThemeState) { - Modifier.sizeIn( - minWidth = width, - minHeight = height - ) - } - } else Modifier + val sizeModifier = composableTheme.intrinsicSizeModifier() CheckboxCore( checked = checked, diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Radio.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Radio.kt index 2f421b6fc..766f0a6d8 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Radio.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Radio.kt @@ -18,11 +18,10 @@ import net.kernelpanicsoft.archie.gui.layout.Renderer import net.kernelpanicsoft.archie.gui.layout.Row import net.kernelpanicsoft.archie.gui.modifiers.Modifier import net.kernelpanicsoft.archie.gui.modifiers.input.selectable -import net.kernelpanicsoft.archie.gui.modifiers.sizeIn import net.kernelpanicsoft.archie.gui.nodes.UINode import net.kernelpanicsoft.archie.gui.theme.LocalTheme -import net.kernelpanicsoft.archie.gui.theme.SimpleThemeState import net.kernelpanicsoft.archie.gui.theme.ThemeVariants +import net.kernelpanicsoft.archie.gui.theme.intrinsicSizeModifier import net.kernelpanicsoft.archie.gui.util.extension.drawThemeState import net.kernelpanicsoft.archie.gui.util.extension.invoke import net.minecraft.client.gui.GuiGraphics @@ -88,11 +87,7 @@ fun RadioButton( val theme = LocalTheme.current val composableTheme = theme.getComposableTheme(texture) val measurePolicy = remember { BoxMeasurePolicy(Alignment.Center) } - val sizeModifier = if (!composableTheme.isNineslice) { - with(composableTheme.states[TextureStates.DEFAULT] as SimpleThemeState) { - Modifier.sizeIn(minWidth = width, minHeight = height) - } - } else Modifier + val sizeModifier = composableTheme.intrinsicSizeModifier() RadioButtonCore( selected = selected, diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/ConfirmDialog.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/ConfirmDialog.kt index d942575c1..3b502f3fb 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/ConfirmDialog.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/ConfirmDialog.kt @@ -22,7 +22,6 @@ import net.kernelpanicsoft.archie.gui.modifiers.Modifier import net.kernelpanicsoft.archie.gui.modifiers.position.margin import net.kernelpanicsoft.archie.gui.modifiers.position.offset import net.kernelpanicsoft.archie.gui.modifiers.position.padding -import net.kernelpanicsoft.archie.gui.modifiers.sizeIn import net.kernelpanicsoft.archie.gui.theme.LocalTheme import net.minecraft.network.chat.Component import kotlinx.coroutines.delay @@ -95,12 +94,10 @@ fun ModalScope.ConfirmDialog( Button( onClick = { closeWithAnimation(onConfirm) }, enabled = !closing, - modifier = Modifier.sizeIn(minWidth = 50, minHeight = 20) ) { Text(confirmText) } Button( onClick = { closeWithAnimation(onCancel) }, enabled = !closing, - modifier = Modifier.sizeIn(minWidth = 50, minHeight = 20) ) { Text(cancelText) } } } diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/DialogPrimitives.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/DialogPrimitives.kt index f7eadf792..b03497f08 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/DialogPrimitives.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/DialogPrimitives.kt @@ -83,7 +83,6 @@ fun ModalScope.AlertDialog( onConfirm() dismiss() }, - modifier = Modifier.sizeIn(minWidth = 50, minHeight = 20), ) { Text(confirmText, dropShadow = false) } @@ -136,7 +135,6 @@ fun ModalScope.PromptDialog( onCancel() dismiss() }, - modifier = Modifier.sizeIn(minWidth = 50, minHeight = 20), ) { Text(cancelText, dropShadow = false) } @@ -146,7 +144,6 @@ fun ModalScope.PromptDialog( onConfirm(value) dismiss() }, - modifier = Modifier.sizeIn(minWidth = 50, minHeight = 20), ) { Text(confirmText, dropShadow = false) } @@ -186,7 +183,7 @@ fun ModalScope.ChoiceDialog( choices.forEach { choice -> Button( enabled = choice.enabled, - modifier = Modifier.sizeIn(minWidth = 150, maxWidth = 150, minHeight = 20), + modifier = Modifier.width(150), onClick = { onSelected(choice.value) dismiss() @@ -204,7 +201,6 @@ fun ModalScope.ChoiceDialog( onCancel() dismiss() }, - modifier = Modifier.sizeIn(minWidth = 50, minHeight = 20), ) { Text(cancelText, dropShadow = false) } diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/theme/ComposableTheme.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/theme/ComposableTheme.kt index 631b2428e..bf9d4c041 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/theme/ComposableTheme.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/theme/ComposableTheme.kt @@ -6,6 +6,8 @@ import kotlinx.serialization.Serializable import net.kernelpanicsoft.archie.Archie import net.kernelpanicsoft.archie.gui.composables.theme.TextureStates import net.kernelpanicsoft.archie.gui.layout.Size +import net.kernelpanicsoft.archie.gui.modifiers.Modifier +import net.kernelpanicsoft.archie.gui.modifiers.sizeIn import net.kernelpanicsoft.archie.resourcepacks.SerializationReloadListener import net.kernelpanicsoft.archie.serialization.SerializationManager import net.kernelpanicsoft.archie.serialization.serializers.SResourceLocation @@ -58,6 +60,7 @@ data class StatefulTheme(val states: Map) data class RawComposableTheme( val states: Map = emptyMap(), val variants: Map> = emptyMap(), + @SerialName("min_size") val minSize: Size? = null, ) /** Raw JSON shape of a single theme state; fields left `null` inherit from the state's `"default"` entry. */ @@ -96,16 +99,22 @@ private data class GuiScalingMetadata( * Contains base [states] and optional named [variants] (e.g. `"dark"`). * * @property isNineslice Whether [states]' default texture is nine-slice scaled, per its - * `.mcmeta` sprite metadata. When `false`, composables using this theme get a minimum - * size matching the sprite's own pixel dimensions instead of stretching arbitrarily. + * `.mcmeta` sprite metadata. When `false` and [minSize] isn't set, composables using this + * theme get a minimum size matching the sprite's own pixel dimensions instead of stretching + * arbitrarily. * @property states Base state map (always contains at least `"default"`). * @property variants Named variant overrides (e.g. `"dark"` → its own state map). + * @property minSize Explicit intrinsic minimum size composables using this theme should + * enforce, regardless of [isNineslice] - e.g. a nine-slice button texture still wants a + * sensible minimum clickable area even though its sprite can stretch to any size. Takes + * priority over the sprite-size fallback described under [isNineslice] when set. */ @Serializable data class ComposableTheme( val isNineslice: Boolean = false, val states: Map, val variants: Map = emptyMap(), + val minSize: Size? = null, ) { companion object { /** @@ -136,6 +145,20 @@ data class ComposableTheme( (variantName?.let { variants[it] }?.states?.get(stateName) ?: states[stateName]) != null } +/** + * The [Modifier.sizeIn] floor a composable using this theme should apply to its own layout + * node: [ComposableTheme.minSize] when the theme declares one, otherwise the `"default"` + * state's own sprite dimensions for a non-nine-slice texture (which can't stretch without + * distorting), otherwise no floor at all - a nine-slice texture with no explicit [ComposableTheme.minSize] + * is free to shrink or stretch to fit its content. + */ +fun ComposableTheme.intrinsicSizeModifier(): Modifier { + minSize?.let { return Modifier.sizeIn(minWidth = it.width, minHeight = it.height) } + if (isNineslice) return Modifier + val default = states[TextureStates.DEFAULT] as SimpleThemeState + return Modifier.sizeIn(minWidth = default.width, minHeight = default.height) +} + /* ─────────────────────── Reload listener ─────────────────────── */ /** @@ -156,7 +179,8 @@ data class ComposableTheme( * "height": 20 * }, * "focused": { "texture": "archie:java/button_highlighted" } - * } + * }, + * "min_size": { "width": 50, "height": 20 } * } * ``` */ @@ -211,10 +235,10 @@ class ThemeResourceListener : } val isNineslice = resourceManager.isNineSliceTexture(defaultState.texture) - COMPOSABLES[location] = ComposableTheme(isNineslice, states, variants) + COMPOSABLES[location] = ComposableTheme(isNineslice, states, variants, root.minSize) Archie.LOGGER.info( - "Theme \"{}\" loaded ({} states, {} variants, nineslice={})", - location, states.size, variants.size, isNineslice, + "Theme \"{}\" loaded ({} states, {} variants, nineslice={}, minSize={})", + location, states.size, variants.size, isNineslice, root.minSize, ) } catch (e: Exception) { Archie.LOGGER.warn("Error processing theme at {}: {}", location, e.message, e) diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/button.json b/core/common/src/main/resources/assets/archie/archie_themes/java/button.json index 29aefb904..83d47cc58 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/java/button.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/button.json @@ -15,5 +15,9 @@ "disabled": { "texture": "archie:java/button_disabled" } + }, + "min_size": { + "width": 50, + "height": 20 } } From 5bbd4eb7c5ff35102e052b552913e5e45e095058 Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Wed, 12 Aug 2026 15:48:46 -0400 Subject: [PATCH 13/30] Add content_padding theme property, center dialog actions, fold ConfirmDialog into DialogPrimitives Button's label text rendered flush against the button's edges once it grew past its min_size floor to fit a longer label - nothing reserved inner spacing around content. Added a content_padding theme property (ThemePadding: horizontal/vertical), symmetric to min_size, exposed via ComposableTheme.contentPaddingModifier() and wired into Button; button.json declares 4x2. Fixed dialog action rows rendering pinned to the Column's left edge instead of spread across the dialog - Column resets minWidth to 0 before measuring each child, so a Modifier.fillMaxWidth() on the action row (the first fix attempted) would have expanded it toward the screen's own incoming max width rather than the dialog's, the same unbounded-fill mistake as the earlier Collapsible bug. The real cause is Column placing children via horizontalAlignment.align(...) against its own resolved width, defaulting to Alignment.Start - fixed by centering ModalDialogScaffold's and ConfirmDialog's Column instead, which carries no such risk since it only repositions, never resizes. Folded ConfirmDialog into DialogPrimitives.kt as a proper ModalDialogScaffold-based primitive, alongside AlertDialog/ PromptDialog/ChoiceDialog. It previously reimplemented its own entered/closing/offsetY animation state, entirely redundant with what modal()/ModalLayout already drives generically for every dialog via the shared transitionProgress - worse, ConfirmDialog's own .offset() compounded on top of ModalLayout's own offset, and its manual delay before calling dismiss() meant closing played two animations back to back instead of one shared fade+slide. Full live GameTest suite: 21/21 passing. --- .../archie/gui/composables/input/Button.kt | 6 +- .../gui/composables/modal/ConfirmDialog.kt | 105 ------------------ .../gui/composables/modal/DialogPrimitives.kt | 46 +++++++- .../archie/gui/theme/ComposableTheme.kt | 39 ++++++- .../archie/archie_themes/java/button.json | 4 + 5 files changed, 88 insertions(+), 112 deletions(-) delete mode 100644 core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/ConfirmDialog.kt diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt index 5bb52fe7d..2d0a0702d 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Button.kt @@ -16,6 +16,7 @@ import net.kernelpanicsoft.archie.gui.modifiers.position.offset import net.kernelpanicsoft.archie.gui.nodes.UINode import net.kernelpanicsoft.archie.gui.theme.LocalTheme import net.kernelpanicsoft.archie.gui.theme.ThemeVariants +import net.kernelpanicsoft.archie.gui.theme.contentPaddingModifier import net.kernelpanicsoft.archie.gui.theme.intrinsicSizeModifier import net.kernelpanicsoft.archie.gui.util.extension.drawThemeState import net.kernelpanicsoft.archie.gui.util.extension.invoke @@ -85,7 +86,10 @@ fun Button( drawThemeState(state, x, y, node.width, node.height) } }, - modifier = modifier.then(composableTheme.intrinsicSizeModifier()).offset(x = 0, y = pressOffset) + modifier = modifier + .then(composableTheme.intrinsicSizeModifier()) + .then(composableTheme.contentPaddingModifier()) + .offset(x = 0, y = pressOffset) ) } } diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/ConfirmDialog.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/ConfirmDialog.kt deleted file mode 100644 index 3b502f3fb..000000000 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/ConfirmDialog.kt +++ /dev/null @@ -1,105 +0,0 @@ -package net.kernelpanicsoft.archie.gui.composables.modal - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import net.kernelpanicsoft.archie.gui.animation.AnimationSpec -import net.kernelpanicsoft.archie.gui.animation.Easings -import net.kernelpanicsoft.archie.gui.animation.animateInt -import net.kernelpanicsoft.archie.gui.composables.basic.Text -import net.kernelpanicsoft.archie.gui.composables.containers.Surface -import net.kernelpanicsoft.archie.gui.composables.input.Button -import net.kernelpanicsoft.archie.gui.layer.ModalScope -import net.kernelpanicsoft.archie.gui.layout.Alignment -import net.kernelpanicsoft.archie.gui.layout.Arrangement -import net.kernelpanicsoft.archie.gui.layout.Column -import net.kernelpanicsoft.archie.gui.layout.Row -import net.kernelpanicsoft.archie.gui.modifiers.Modifier -import net.kernelpanicsoft.archie.gui.modifiers.position.margin -import net.kernelpanicsoft.archie.gui.modifiers.position.offset -import net.kernelpanicsoft.archie.gui.modifiers.position.padding -import net.kernelpanicsoft.archie.gui.theme.LocalTheme -import net.minecraft.network.chat.Component -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlin.time.Duration.Companion.milliseconds - -private const val DIALOG_ANIMATION_MS = 180L - -/** - * A generic confirm/cancel modal with custom [content] and a slide/fade dismiss animation. - * - * Unlike [AlertDialog]/[PromptDialog]/[ChoiceDialog], [content] is fully custom rather than a - * fixed message layout. [net.kernelpanicsoft.archie.gui.layer.ModalScope.dismiss] is deferred until the close animation finishes so the modal - * doesn't disappear abruptly. - * - * @param title The dialog's header text. - * @param confirmText Label for the confirm button. - * @param cancelText Label for the cancel button. - * @param onConfirm Called immediately when the confirm button is pressed, before the close - * animation plays. - * @param onCancel Called immediately when the cancel button is pressed, before the close - * animation plays. - * @param content The dialog body, shown above the action row. - */ -@Composable -fun ModalScope.ConfirmDialog( - title: Component = Component.literal("Confirm Dialog"), - confirmText: Component = Component.literal("Confirm"), - cancelText: Component = Component.literal("Cancel"), - onConfirm: () -> Unit = {}, - onCancel: () -> Unit = {}, - content: @Composable () -> Unit -) -{ - val scope = rememberCoroutineScope() - var entered by remember { mutableStateOf(false) } - var closing by remember { mutableStateOf(false) } - LaunchedEffect(Unit) { entered = true } - - fun closeWithAnimation(action: () -> Unit) { - if (closing) return - closing = true - entered = false - action() - scope.launch { - delay(DIALOG_ANIMATION_MS.milliseconds) - dismiss() - } - } - - val offsetY = animateInt( - targetValue = if (entered) 0 else 8, - spec = AnimationSpec(durationMillis = DIALOG_ANIMATION_MS.milliseconds, easing = Easings.OutCubic), - ) - - Surface(modifier = Modifier.padding(4).offset(x = 0, y = offsetY)) { - Column(modifier = Modifier.margin(4)) { - Text( - text = title, - modifier = Modifier.margin(bottom = 4), - color = LocalTheme.current.darkTextColor, - dropShadow = false - ) - content() - Row( - modifier = Modifier.margin(top = 4), - horizontalArrangement = Arrangement.SpaceEvenly, - verticalAlignment = Alignment.CenterVertically - ) { - Button( - onClick = { closeWithAnimation(onConfirm) }, - enabled = !closing, - ) { Text(confirmText) } - Button( - onClick = { closeWithAnimation(onCancel) }, - enabled = !closing, - ) { Text(cancelText) } - } - } - } -} \ No newline at end of file diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/DialogPrimitives.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/DialogPrimitives.kt index b03497f08..9db249299 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/DialogPrimitives.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/modal/DialogPrimitives.kt @@ -40,7 +40,7 @@ private fun ModalDialogScaffold( // Padding on the Surface itself, not margin on the inner Column, matching ConfirmDialog - // margin only grows the parent, it doesn't shrink what content measures against. Surface(modifier = Modifier.padding(4).then(modifier)) { - Column(verticalArrangement = Arrangement.spacedBy(4)) { + Column(verticalArrangement = Arrangement.spacedBy(4), horizontalAlignment = Alignment.CenterHorizontally) { Text( text = title, color = LocalTheme.current.darkTextColor, @@ -56,6 +56,50 @@ private fun ModalDialogScaffold( } } +/** + * Generic confirm/cancel modal with fully custom [content], rather than a fixed message layout + * like [AlertDialog]/[PromptDialog]/[ChoiceDialog]. + * + * @param title The dialog's header text. + * @param confirmText Label for the confirm button. + * @param cancelText Label for the cancel button. + * @param onConfirm Called when the confirm button is pressed, just before the modal dismisses itself. + * @param onCancel Called when the cancel button is pressed, just before the modal dismisses itself. + * @param content The dialog body, shown above the action row. + */ +@Composable +fun ModalScope.ConfirmDialog( + title: Component = Component.literal("Confirm Dialog"), + confirmText: Component = Component.literal("Confirm"), + cancelText: Component = Component.literal("Cancel"), + onConfirm: () -> Unit = {}, + onCancel: () -> Unit = {}, + content: @Composable () -> Unit, +) { + ModalDialogScaffold( + title = title, + body = content, + actions = { + Button( + onClick = { + onConfirm() + dismiss() + }, + ) { + Text(confirmText, dropShadow = false) + } + Button( + onClick = { + onCancel() + dismiss() + }, + ) { + Text(cancelText, dropShadow = false) + } + }, + ) +} + /** * Simple one-action modal for acknowledgements and warnings. * diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/theme/ComposableTheme.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/theme/ComposableTheme.kt index bf9d4c041..bf8f35a6f 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/theme/ComposableTheme.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/theme/ComposableTheme.kt @@ -7,10 +7,13 @@ import net.kernelpanicsoft.archie.Archie import net.kernelpanicsoft.archie.gui.composables.theme.TextureStates import net.kernelpanicsoft.archie.gui.layout.Size import net.kernelpanicsoft.archie.gui.modifiers.Modifier +import net.kernelpanicsoft.archie.gui.modifiers.position.padding import net.kernelpanicsoft.archie.gui.modifiers.sizeIn import net.kernelpanicsoft.archie.resourcepacks.SerializationReloadListener import net.kernelpanicsoft.archie.serialization.SerializationManager import net.kernelpanicsoft.archie.serialization.serializers.SResourceLocation +import net.kernelpanicsoft.archie.util.div +import net.kernelpanicsoft.archie.util.plus import net.minecraft.resources.ResourceLocation import net.minecraft.server.packs.resources.PreparableReloadListener import net.minecraft.server.packs.resources.ResourceManager @@ -55,12 +58,27 @@ data class SimpleThemeState( @Serializable data class StatefulTheme(val states: Map) +/** + * Inner spacing a composable using a theme should reserve around its own [content], so e.g. a + * button's label never renders flush against the button's edges once it grows past + * [ComposableTheme.minSize] to fit a longer label. + * + * @property horizontal Padding applied to both the left and right sides. + * @property vertical Padding applied to both the top and bottom sides. + */ +@Serializable +data class ThemePadding( + val horizontal: Int = 0, + val vertical: Int = 0, +) + /** Raw JSON shape of a theme file, deserialized as-is and resolved by [ThemeResourceListener] into a [ComposableTheme]. */ @Serializable data class RawComposableTheme( val states: Map = emptyMap(), val variants: Map> = emptyMap(), @SerialName("min_size") val minSize: Size? = null, + @SerialName("content_padding") val contentPadding: ThemePadding? = null, ) /** Raw JSON shape of a single theme state; fields left `null` inherit from the state's `"default"` entry. */ @@ -108,6 +126,8 @@ private data class GuiScalingMetadata( * enforce, regardless of [isNineslice] - e.g. a nine-slice button texture still wants a * sensible minimum clickable area even though its sprite can stretch to any size. Takes * priority over the sprite-size fallback described under [isNineslice] when set. + * @property contentPadding Inner spacing composables using this theme should reserve around + * their own content, so content grown past [minSize] doesn't render flush against the edges. */ @Serializable data class ComposableTheme( @@ -115,6 +135,7 @@ data class ComposableTheme( val states: Map, val variants: Map = emptyMap(), val minSize: Size? = null, + val contentPadding: ThemePadding? = null, ) { companion object { /** @@ -159,6 +180,13 @@ fun ComposableTheme.intrinsicSizeModifier(): Modifier { return Modifier.sizeIn(minWidth = default.width, minHeight = default.height) } +/** + * The [Modifier.padding] a composable using this theme should reserve around its own content, + * per [ComposableTheme.contentPadding] - a no-op [Modifier] when the theme doesn't declare one. + */ +fun ComposableTheme.contentPaddingModifier(): Modifier = + contentPadding?.let { Modifier.padding(horizontal = it.horizontal, vertical = it.vertical) } ?: Modifier + /* ─────────────────────── Reload listener ─────────────────────── */ /** @@ -180,7 +208,8 @@ fun ComposableTheme.intrinsicSizeModifier(): Modifier { * }, * "focused": { "texture": "archie:java/button_highlighted" } * }, - * "min_size": { "width": 50, "height": 20 } + * "min_size": { "width": 50, "height": 20 }, + * "content_padding": { "horizontal": 4, "vertical": 2 } * } * ``` */ @@ -235,7 +264,7 @@ class ThemeResourceListener : } val isNineslice = resourceManager.isNineSliceTexture(defaultState.texture) - COMPOSABLES[location] = ComposableTheme(isNineslice, states, variants, root.minSize) + COMPOSABLES[location] = ComposableTheme(isNineslice, states, variants, root.minSize, root.contentPadding) Archie.LOGGER.info( "Theme \"{}\" loaded ({} states, {} variants, nineslice={}, minSize={})", location, states.size, variants.size, isNineslice, root.minSize, @@ -286,11 +315,11 @@ class ThemeResourceListener : private fun ResourceManager.isNineSliceTexture(texture: ResourceLocation): Boolean { val candidates = if (texture.path.startsWith("textures/") && texture.path.endsWith(".png")) { - listOf(ResourceLocation.fromNamespaceAndPath(texture.namespace, "${texture.path}.mcmeta")) + listOf(texture + ".mcmeta") } else { listOf( - ResourceLocation.fromNamespaceAndPath(texture.namespace, "textures/gui/sprites/${texture.path}.png.mcmeta"), - ResourceLocation.fromNamespaceAndPath(texture.namespace, "textures/${texture.path}.png.mcmeta"), + "textures" / ("gui" / ("sprites" / texture)) + ".png.mcmeta", + "textures" / texture + ".png.mcmeta", ) } diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/button.json b/core/common/src/main/resources/assets/archie/archie_themes/java/button.json index 83d47cc58..ef09e086a 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/java/button.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/button.json @@ -19,5 +19,9 @@ "min_size": { "width": 50, "height": 20 + }, + "content_padding": { + "horizontal": 4, + "vertical": 2 } } From e479485be2f70a641bef3e0f4a2a934bbbb50cff Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Wed, 12 Aug 2026 16:01:34 -0400 Subject: [PATCH 14/30] Add dark theme variant for all components and toggle/hover animations Dark mode: generated archie_themes/java/dark/.json for every themed composable (button, checkbox, radio, slider, slider_handle, switch_track, switch_thumb, tab_game, tab_menu, text_field, slot, energy_bar, fluid_tank, progress_bar, small_checkbox), following the file-based dark-variant convention already established by the existing dark/surface.json (ThemeData.getComposableTheme resolves "java/dark/" first when Theme(mode = ThemeVariants.DARK) is active). Each dark JSON is the light one with every texture reference rewritten to a "_dark" suffixed variant, min_size/ content_padding preserved unchanged. The 41 "_dark" textures are generated programmatically - a uniform 0.4x RGB multiply (alpha untouched) on each source PNG, preserving every bevel/border/antialiasing pattern since it's a linear scale, not a hue/levels remap. .mcmeta nine-slice companions are copied verbatim alongside their recolored PNG (geometry is unaffected by a color-only change). Added testDarkThemeResolvesRecoloredButtonTexture as a regression check that the dark variant actually resolves to the recolored asset rather than silently falling back to the light theme on a naming mismatch. Animations: added a reusable animatePulse() primitive (Animation.kt) - sweeps from a start value back to a target once per key change, for a one-shot Easings.OutBack overshoot-then-settle "pop" rather than animateFloat/animateInt's continuous track-toward-a-moving-target model. Wired into Checkbox and RadioButton so toggling/selecting pops instead of snapping instantly. Tab's selected-elevation offset now eases via animateInt instead of jumping, matching Button's existing press-offset idiom. Slider's thumb now grows on hover/drag via animateFloat instead of staying a fixed size regardless of interaction (Switch's thumb-slide animation already existed - no changes needed there). Full live GameTest suite: 22/22 passing. --- .../archie/gui/animation/Animation.kt | 43 ++++++++++++++++++ .../composables/containers/TabContainer.kt | 11 ++++- .../archie/gui/composables/input/Checkbox.kt | 22 ++++++++- .../archie/gui/composables/input/Radio.kt | 22 ++++++++- .../archie/gui/composables/input/Slider.kt | 20 +++++++- .../archie_themes/java/dark/button.json | 27 +++++++++++ .../archie_themes/java/dark/checkbox.json | 22 +++++++++ .../archie_themes/java/dark/energy_bar.json | 13 ++++++ .../archie_themes/java/dark/fluid_tank.json | 13 ++++++ .../archie_themes/java/dark/progress_bar.json | 13 ++++++ .../archie/archie_themes/java/dark/radio.json | 25 ++++++++++ .../archie_themes/java/dark/slider.json | 19 ++++++++ .../java/dark/slider_handle.json | 19 ++++++++ .../archie/archie_themes/java/dark/slot.json | 15 ++++++ .../java/dark/small_checkbox.json | 16 +++++++ .../archie_themes/java/dark/switch_thumb.json | 16 +++++++ .../archie_themes/java/dark/switch_track.json | 25 ++++++++++ .../archie_themes/java/dark/tab_game.json | 25 ++++++++++ .../archie_themes/java/dark/tab_menu.json | 25 ++++++++++ .../archie_themes/java/dark/text_field.json | 16 +++++++ .../textures/gui/sprites/java/button_dark.png | Bin 0 -> 1068 bytes .../gui/sprites/java/button_dark.png.mcmeta | 10 ++++ .../gui/sprites/java/button_disabled_dark.png | Bin 0 -> 955 bytes .../java/button_disabled_dark.png.mcmeta | 10 ++++ .../sprites/java/button_highlighted_dark.png | Bin 0 -> 1080 bytes .../java/button_highlighted_dark.png.mcmeta | 10 ++++ .../checkbox_clicked_and_focused_dark.png | Bin 0 -> 393 bytes .../sprites/java/checkbox_clicked_dark.png | Bin 0 -> 364 bytes .../gui/sprites/java/checkbox_dark.png | Bin 0 -> 304 bytes .../sprites/java/checkbox_focused_dark.png | Bin 0 -> 310 bytes .../gui/sprites/java/energy_bar_dark.png | Bin 0 -> 136 bytes .../sprites/java/energy_bar_dark.png.mcmeta | 10 ++++ .../gui/sprites/java/fluid_tank_dark.png | Bin 0 -> 133 bytes .../sprites/java/fluid_tank_dark.png.mcmeta | 10 ++++ .../gui/sprites/java/progress_bar_dark.png | Bin 0 -> 115 bytes .../sprites/java/progress_bar_dark.png.mcmeta | 10 ++++ .../java/radio_clicked_and_focused_dark.png | Bin 0 -> 319 bytes .../gui/sprites/java/radio_clicked_dark.png | Bin 0 -> 329 bytes .../textures/gui/sprites/java/radio_dark.png | Bin 0 -> 323 bytes .../gui/sprites/java/radio_disabled_dark.png | Bin 0 -> 321 bytes .../gui/sprites/java/radio_focused_dark.png | Bin 0 -> 328 bytes .../textures/gui/sprites/java/slider_dark.png | Bin 0 -> 1710 bytes .../gui/sprites/java/slider_dark.png.mcmeta | 10 ++++ .../gui/sprites/java/slider_handle_dark.png | Bin 0 -> 232 bytes .../java/slider_handle_dark.png.mcmeta | 15 ++++++ .../java/slider_handle_highlighted_dark.png | Bin 0 -> 220 bytes .../slider_handle_highlighted_dark.png.mcmeta | 15 ++++++ .../sprites/java/slider_highlighted_dark.png | Bin 0 -> 1756 bytes .../java/slider_highlighted_dark.png.mcmeta | 10 ++++ .../textures/gui/sprites/java/slot_dark.png | Bin 0 -> 511 bytes .../java/small_checkbox_clicked_dark.png | Bin 0 -> 279 bytes .../gui/sprites/java/small_checkbox_dark.png | Bin 0 -> 196 bytes .../gui/sprites/java/switch_thumb_dark.png | Bin 0 -> 126 bytes .../java/switch_thumb_disabled_dark.png | Bin 0 -> 127 bytes .../switch_track_clicked_and_focused_dark.png | Bin 0 -> 786 bytes .../java/switch_track_clicked_dark.png | Bin 0 -> 777 bytes .../gui/sprites/java/switch_track_dark.png | Bin 0 -> 428 bytes .../java/switch_track_disabled_dark.png | Bin 0 -> 436 bytes .../java/switch_track_focused_dark.png | Bin 0 -> 434 bytes .../gui/sprites/java/tab_game_dark.png | Bin 0 -> 155 bytes .../gui/sprites/java/tab_game_dark.png.mcmeta | 15 ++++++ .../sprites/java/tab_game_disabled_dark.png | Bin 0 -> 167 bytes .../java/tab_game_disabled_dark.png.mcmeta | 15 ++++++ .../sprites/java/tab_game_focused_dark.png | Bin 0 -> 154 bytes .../java/tab_game_focused_dark.png.mcmeta | 15 ++++++ .../sprites/java/tab_game_selected_dark.png | Bin 0 -> 174 bytes .../java/tab_game_selected_dark.png.mcmeta | 15 ++++++ .../tab_game_selected_highlighted_dark.png | Bin 0 -> 178 bytes ..._game_selected_highlighted_dark.png.mcmeta | 15 ++++++ .../gui/sprites/java/tab_menu_dark.png | Bin 0 -> 182 bytes .../gui/sprites/java/tab_menu_dark.png.mcmeta | 10 ++++ .../sprites/java/tab_menu_disabled_dark.png | Bin 0 -> 182 bytes .../java/tab_menu_disabled_dark.png.mcmeta | 10 ++++ .../sprites/java/tab_menu_focused_dark.png | Bin 0 -> 187 bytes .../java/tab_menu_focused_dark.png.mcmeta | 10 ++++ .../sprites/java/tab_menu_selected_dark.png | Bin 0 -> 191 bytes .../java/tab_menu_selected_dark.png.mcmeta | 10 ++++ .../tab_menu_selected_highlighted_dark.png | Bin 0 -> 190 bytes ..._menu_selected_highlighted_dark.png.mcmeta | 10 ++++ .../gui/sprites/java/text_field_dark.png | Bin 0 -> 110 bytes .../sprites/java/text_field_dark.png.mcmeta | 10 ++++ .../java/text_field_highlighted_dark.png | Bin 0 -> 110 bytes .../text_field_highlighted_dark.png.mcmeta | 10 ++++ .../internal/tests/InputComponentsGameTest.kt | 19 ++++++++ 84 files changed, 677 insertions(+), 4 deletions(-) create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/java/dark/button.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/java/dark/checkbox.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/java/dark/energy_bar.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/java/dark/fluid_tank.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/java/dark/progress_bar.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/java/dark/radio.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/java/dark/slider.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/java/dark/slider_handle.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/java/dark/slot.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/java/dark/small_checkbox.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/java/dark/switch_thumb.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/java/dark/switch_track.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/java/dark/tab_game.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/java/dark/tab_menu.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/java/dark/text_field.json create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/button_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/button_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/button_disabled_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/button_disabled_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/button_highlighted_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/button_highlighted_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/checkbox_clicked_and_focused_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/checkbox_clicked_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/checkbox_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/checkbox_focused_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/energy_bar_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/energy_bar_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/fluid_tank_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/fluid_tank_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/progress_bar_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/progress_bar_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/radio_clicked_and_focused_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/radio_clicked_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/radio_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/radio_disabled_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/radio_focused_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_handle_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_handle_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_handle_highlighted_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_handle_highlighted_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_highlighted_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_highlighted_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slot_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/small_checkbox_clicked_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/small_checkbox_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/switch_thumb_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/switch_thumb_disabled_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/switch_track_clicked_and_focused_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/switch_track_clicked_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/switch_track_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/switch_track_disabled_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/switch_track_focused_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_disabled_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_disabled_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_focused_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_focused_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_selected_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_selected_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_selected_highlighted_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_selected_highlighted_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_disabled_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_disabled_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_focused_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_focused_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_selected_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_selected_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_selected_highlighted_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_selected_highlighted_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/text_field_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/text_field_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/text_field_highlighted_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/text_field_highlighted_dark.png.mcmeta diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/animation/Animation.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/animation/Animation.kt index 71af0cec7..3774dc7a6 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/animation/Animation.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/animation/Animation.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.withFrameNanos @@ -91,4 +92,46 @@ fun animateInt(targetValue: Int, spec: AnimationSpec = AnimationSpec()): Int { return animatedFloat.roundToInt() } +/** + * Sweeps from [from] to [to] every time [key] changes, holding at [to] otherwise - a one-shot + * "pulse"/"pop" effect for a discrete event (a checkbox toggling, a radio button being + * selected) rather than the continuous value [animateFloat]/[animateInt] track toward a moving + * target. Pair with [Easings.OutBack] for a satisfying overshoot-then-settle bounce. + * + * Does not pulse on the composable's initial composition - only on a later change of [key]. + */ +@Composable +fun animatePulse(key: Any?, from: Float = 0.8f, to: Float = 1f, spec: AnimationSpec = AnimationSpec()): Float { + var value by remember { mutableFloatStateOf(to) } + var initialized by remember { mutableStateOf(false) } + + LaunchedEffect(key) { + if (!initialized) { + initialized = true + return@LaunchedEffect + } + + val duration = spec.durationMillis + if (duration <= 0.milliseconds) { + value = to + return@LaunchedEffect + } + + value = from + val startTime = withFrameNanos { it } + var frameTime = startTime + var rawProgress: Float + do { + val elapsedNanos = frameTime - startTime + rawProgress = (elapsedNanos / (duration.inWholeMilliseconds * 1_000_000f)).coerceIn(0f, 1f) + value = from + (to - from) * spec.easing.transform(rawProgress) + frameTime = withFrameNanos { it } + } while (rawProgress < 1f) + + value = to + } + + return value +} + diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/TabContainer.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/TabContainer.kt index ea9de0680..51169d9e2 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/TabContainer.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/containers/TabContainer.kt @@ -6,6 +6,8 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import net.kernelpanicsoft.archie.gui.animation.AnimationSpec +import net.kernelpanicsoft.archie.gui.animation.animateInt import net.kernelpanicsoft.archie.gui.composables.basic.Text import net.kernelpanicsoft.archie.gui.composables.basic.Texture import net.kernelpanicsoft.archie.gui.composables.theme.WidgetState @@ -36,6 +38,7 @@ import net.kernelpanicsoft.archie.gui.util.extension.invoke import net.minecraft.network.chat.Component import net.minecraft.resources.ResourceLocation import net.minecraft.client.gui.GuiGraphics +import kotlin.time.Duration.Companion.milliseconds /** Built-in themed texture keys for [Tab]/[TabContainer], matching vanilla tab styles. */ object TabTextures @@ -361,9 +364,15 @@ fun Tab( enabled = enabled, ) val state = composableTheme.getState(stateKey, variant) + // Eases into/out of the selected elevation instead of snapping, matching Button's own + // press-offset animation. + val elevationOffset = animateInt( + targetValue = if (selected && !elevateSelected) -SELECTED_ELEVATION_PX else 0, + spec = AnimationSpec(durationMillis = 120.milliseconds), + ) val offsetModifier = Modifier .zIndex(if (selected && elevateSelected) 1f else 0f) - .offset(x = 0, y = if (selected && !elevateSelected) -SELECTED_ELEVATION_PX else 0) + .offset(x = 0, y = elevationOffset) .padding(horizontal = 10, vertical = 6) val sizeModifier = composableTheme.intrinsicSizeModifier() diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Checkbox.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Checkbox.kt index 6feba74cf..9e223ad7d 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Checkbox.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Checkbox.kt @@ -1,6 +1,9 @@ package net.kernelpanicsoft.archie.gui.composables.input import androidx.compose.runtime.* +import net.kernelpanicsoft.archie.gui.animation.AnimationSpec +import net.kernelpanicsoft.archie.gui.animation.Easings +import net.kernelpanicsoft.archie.gui.animation.animatePulse import net.kernelpanicsoft.archie.gui.composables.theme.WidgetState import net.kernelpanicsoft.archie.gui.interaction.MutableInteractionSource import net.kernelpanicsoft.archie.gui.interaction.collectIsFocusedAsState @@ -18,6 +21,8 @@ import net.kernelpanicsoft.archie.gui.theme.intrinsicSizeModifier import net.kernelpanicsoft.archie.gui.util.extension.drawThemeState import net.kernelpanicsoft.archie.gui.util.extension.invoke import net.minecraft.client.gui.GuiGraphics +import kotlin.math.roundToInt +import kotlin.time.Duration.Companion.milliseconds /** * A standard themed checkbox. @@ -46,6 +51,13 @@ fun Checkbox( val theme = LocalTheme.current val composableTheme = theme.getComposableTheme(texture) val sizeModifier = composableTheme.intrinsicSizeModifier() + // A brief overshoot-then-settle pop whenever `checked` flips, rather than the check mark + // snapping in/out instantly - purely cosmetic, so it's scaled around the node's own center + // instead of touching layout size. + val pop = animatePulse( + key = checked, + spec = AnimationSpec(durationMillis = 160.milliseconds, easing = Easings.OutBack), + ) CheckboxCore( checked = checked, @@ -74,7 +86,15 @@ fun Checkbox( node.renderState = stateKey val state = composableTheme.getState(stateKey, variant) - drawThemeState(state, x, y, node.width, node.height) + val drawWidth = (node.width * pop).roundToInt() + val drawHeight = (node.height * pop).roundToInt() + drawThemeState( + state, + x + (node.width - drawWidth) / 2, + y + (node.height - drawHeight) / 2, + drawWidth, + drawHeight, + ) } }, modifier = checkboxModifier.then(sizeModifier).then(modifier) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Radio.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Radio.kt index 766f0a6d8..17206c819 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Radio.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Radio.kt @@ -3,6 +3,9 @@ package net.kernelpanicsoft.archie.gui.composables.input import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import net.kernelpanicsoft.archie.gui.animation.AnimationSpec +import net.kernelpanicsoft.archie.gui.animation.Easings +import net.kernelpanicsoft.archie.gui.animation.animatePulse import net.kernelpanicsoft.archie.gui.composables.basic.Text import net.kernelpanicsoft.archie.gui.composables.theme.TextureStates import net.kernelpanicsoft.archie.gui.composables.theme.WidgetState @@ -26,6 +29,8 @@ import net.kernelpanicsoft.archie.gui.util.extension.drawThemeState import net.kernelpanicsoft.archie.gui.util.extension.invoke import net.minecraft.client.gui.GuiGraphics import net.minecraft.network.chat.Component +import kotlin.math.roundToInt +import kotlin.time.Duration.Companion.milliseconds /** * Low-level unstyled radio-button behavior, built on [Modifier.selectable]. @@ -88,6 +93,13 @@ fun RadioButton( val composableTheme = theme.getComposableTheme(texture) val measurePolicy = remember { BoxMeasurePolicy(Alignment.Center) } val sizeModifier = composableTheme.intrinsicSizeModifier() + // A brief overshoot-then-settle pop whenever `selected` flips, rather than the dot + // snapping in/out instantly - purely cosmetic, so it's scaled around the node's own + // center instead of touching layout size. + val pop = animatePulse( + key = selected, + spec = AnimationSpec(durationMillis = 160.milliseconds, easing = Easings.OutBack), + ) RadioButtonCore( selected = selected, @@ -116,7 +128,15 @@ fun RadioButton( node.renderState = stateKey val state = composableTheme.getState(stateKey, variant) - drawThemeState(state, x, y, node.width, node.height) + val drawWidth = (node.width * pop).roundToInt() + val drawHeight = (node.height * pop).roundToInt() + drawThemeState( + state, + x + (node.width - drawWidth) / 2, + y + (node.height - drawHeight) / 2, + drawWidth, + drawHeight, + ) } }, ) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt index 21645d141..591df13ba 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt @@ -5,6 +5,8 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import net.kernelpanicsoft.archie.gui.animation.AnimationSpec +import net.kernelpanicsoft.archie.gui.animation.animateFloat import net.kernelpanicsoft.archie.gui.interaction.DragInteraction import net.kernelpanicsoft.archie.gui.interaction.MutableInteractionSource import net.kernelpanicsoft.archie.gui.interaction.collectIsFocusedAsState @@ -31,6 +33,7 @@ import net.kernelpanicsoft.archie.gui.util.extension.invoke import net.minecraft.client.gui.GuiGraphics import org.lwjgl.glfw.GLFW import kotlin.math.roundToInt +import kotlin.time.Duration.Companion.milliseconds private const val SLIDER_MIN_WIDTH = 96 private const val SLIDER_MIN_HEIGHT = 20 @@ -187,6 +190,12 @@ fun Slider( onValueChangeFinished = onValueChangeFinished, modifier = sizeModifier.then(modifier), ) { hovered, dragging, focused, normalizedValue -> + // Grows the thumb slightly on hover/drag instead of it staying a fixed size regardless + // of interaction, matching common slider affordance conventions. + val thumbScale = animateFloat( + targetValue = if (enabled && (hovered || dragging)) 1.25f else 1f, + spec = AnimationSpec(durationMillis = 120.milliseconds), + ) Layout( name = "Slider", measurePolicy = measurePolicy, @@ -223,7 +232,16 @@ fun Slider( drawThemeState(trackState, x, y, node.width, node.height) fill(trackStart, trackY, fillEnd, trackY + SLIDER_TRACK_HEIGHT, fillColor) - drawThemeState(thumbState, thumbX, thumbY, SLIDER_THUMB_WIDTH, SLIDER_THUMB_HEIGHT) + + val drawThumbWidth = (SLIDER_THUMB_WIDTH * thumbScale).roundToInt() + val drawThumbHeight = (SLIDER_THUMB_HEIGHT * thumbScale).roundToInt() + drawThemeState( + thumbState, + thumbX + (SLIDER_THUMB_WIDTH - drawThumbWidth) / 2, + thumbY + (SLIDER_THUMB_HEIGHT - drawThumbHeight) / 2, + drawThumbWidth, + drawThumbHeight, + ) } }, ) diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/dark/button.json b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/button.json new file mode 100644 index 000000000..b086b3ec6 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/button.json @@ -0,0 +1,27 @@ +{ + "states": { + "default": { + "texture": "archie:java/button_dark", + "texture_size": { + "width": 64, + "height": 64 + }, + "width": 64, + "height": 20 + }, + "focused": { + "texture": "archie:java/button_highlighted_dark" + }, + "disabled": { + "texture": "archie:java/button_disabled_dark" + } + }, + "min_size": { + "width": 50, + "height": 20 + }, + "content_padding": { + "horizontal": 4, + "vertical": 2 + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/dark/checkbox.json b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/checkbox.json new file mode 100644 index 000000000..88adc7de9 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/checkbox.json @@ -0,0 +1,22 @@ +{ + "states": { + "default": { + "texture": "archie:java/checkbox_dark", + "texture_size": { + "width": 20, + "height": 20 + }, + "width": 20, + "height": 20 + }, + "focused": { + "texture": "archie:java/checkbox_focused_dark" + }, + "clicked": { + "texture": "archie:java/checkbox_clicked_dark" + }, + "clicked_and_focused": { + "texture": "archie:java/checkbox_clicked_and_focused_dark" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/dark/energy_bar.json b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/energy_bar.json new file mode 100644 index 000000000..1083b8a9e --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/energy_bar.json @@ -0,0 +1,13 @@ +{ + "states": { + "default": { + "texture": "archie:java/energy_bar_dark", + "texture_size": { + "width": 32, + "height": 16 + }, + "width": 32, + "height": 16 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/dark/fluid_tank.json b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/fluid_tank.json new file mode 100644 index 000000000..fbd4b1fdd --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/fluid_tank.json @@ -0,0 +1,13 @@ +{ + "states": { + "default": { + "texture": "archie:java/fluid_tank_dark", + "texture_size": { + "width": 18, + "height": 54 + }, + "width": 18, + "height": 54 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/dark/progress_bar.json b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/progress_bar.json new file mode 100644 index 000000000..8506fd2f8 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/progress_bar.json @@ -0,0 +1,13 @@ +{ + "states": { + "default": { + "texture": "archie:java/progress_bar_dark", + "texture_size": { + "width": 32, + "height": 16 + }, + "width": 32, + "height": 16 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/dark/radio.json b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/radio.json new file mode 100644 index 000000000..fc103dc6a --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/radio.json @@ -0,0 +1,25 @@ +{ + "states": { + "default": { + "texture": "archie:java/radio_dark", + "texture_size": { + "width": 20, + "height": 20 + }, + "width": 20, + "height": 20 + }, + "focused": { + "texture": "archie:java/radio_focused_dark" + }, + "clicked": { + "texture": "archie:java/radio_clicked_dark" + }, + "clicked_and_focused": { + "texture": "archie:java/radio_clicked_and_focused_dark" + }, + "disabled": { + "texture": "archie:java/radio_disabled_dark" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/dark/slider.json b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/slider.json new file mode 100644 index 000000000..b316f8780 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/slider.json @@ -0,0 +1,19 @@ +{ + "states": { + "default": { + "texture": "archie:java/slider_dark", + "texture_size": { + "width": 200, + "height": 20 + }, + "width": 200, + "height": 20 + }, + "focused": { + "texture": "archie:java/slider_highlighted_dark" + }, + "clicked": { + "texture": "archie:java/slider_highlighted_dark" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/dark/slider_handle.json b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/slider_handle.json new file mode 100644 index 000000000..68a14c9f2 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/slider_handle.json @@ -0,0 +1,19 @@ +{ + "states": { + "default": { + "texture": "archie:java/slider_handle_dark", + "texture_size": { + "width": 8, + "height": 20 + }, + "width": 8, + "height": 20 + }, + "focused": { + "texture": "archie:java/slider_handle_highlighted_dark" + }, + "clicked": { + "texture": "archie:java/slider_handle_highlighted_dark" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/dark/slot.json b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/slot.json new file mode 100644 index 000000000..f61fc4ffb --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/slot.json @@ -0,0 +1,15 @@ +{ + "states": { + "default": { + "texture": "archie:java/slot_dark", + "texture_size": { + "width": 18, + "height": 18 + }, + "width": 18, + "height": 18, + "uWidth": 18, + "vHeight": 18 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/dark/small_checkbox.json b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/small_checkbox.json new file mode 100644 index 000000000..640eec64f --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/small_checkbox.json @@ -0,0 +1,16 @@ +{ + "states": { + "default": { + "texture": "archie:java/small_checkbox_dark", + "texture_size": { + "width": 13, + "height": 13 + }, + "width": 13, + "height": 13 + }, + "clicked": { + "texture": "archie:java/small_checkbox_clicked_dark" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/dark/switch_thumb.json b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/switch_thumb.json new file mode 100644 index 000000000..4624f7b22 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/switch_thumb.json @@ -0,0 +1,16 @@ +{ + "states": { + "default": { + "texture": "archie:java/switch_thumb_dark", + "texture_size": { + "width": 14, + "height": 14 + }, + "width": 14, + "height": 14 + }, + "disabled": { + "texture": "archie:java/switch_thumb_disabled_dark" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/dark/switch_track.json b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/switch_track.json new file mode 100644 index 000000000..b2e115931 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/switch_track.json @@ -0,0 +1,25 @@ +{ + "states": { + "default": { + "texture": "archie:java/switch_track_dark", + "texture_size": { + "width": 34, + "height": 18 + }, + "width": 34, + "height": 18 + }, + "focused": { + "texture": "archie:java/switch_track_focused_dark" + }, + "clicked": { + "texture": "archie:java/switch_track_clicked_dark" + }, + "clicked_and_focused": { + "texture": "archie:java/switch_track_clicked_and_focused_dark" + }, + "disabled": { + "texture": "archie:java/switch_track_disabled_dark" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/dark/tab_game.json b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/tab_game.json new file mode 100644 index 000000000..bcf3a26ee --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/tab_game.json @@ -0,0 +1,25 @@ +{ + "states": { + "default": { + "texture": "archie:java/tab_game_dark", + "texture_size": { + "width": 26, + "height": 32 + }, + "width": 26, + "height": 32 + }, + "focused": { + "texture": "archie:java/tab_game_focused_dark" + }, + "clicked": { + "texture": "archie:java/tab_game_selected_dark" + }, + "clicked_and_focused": { + "texture": "archie:java/tab_game_selected_highlighted_dark" + }, + "disabled": { + "texture": "archie:java/tab_game_disabled_dark" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/dark/tab_menu.json b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/tab_menu.json new file mode 100644 index 000000000..dcf4dacd2 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/tab_menu.json @@ -0,0 +1,25 @@ +{ + "states": { + "default": { + "texture": "archie:java/tab_menu_dark", + "texture_size": { + "width": 130, + "height": 24 + }, + "width": 130, + "height": 24 + }, + "focused": { + "texture": "archie:java/tab_menu_focused_dark" + }, + "clicked": { + "texture": "archie:java/tab_menu_selected_dark" + }, + "clicked_and_focused": { + "texture": "archie:java/tab_menu_selected_highlighted_dark" + }, + "disabled": { + "texture": "archie:java/tab_menu_disabled_dark" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/dark/text_field.json b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/text_field.json new file mode 100644 index 000000000..63ad9d183 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/text_field.json @@ -0,0 +1,16 @@ +{ + "states": { + "default": { + "texture": "archie:java/text_field_dark", + "texture_size": { + "width": 16, + "height": 16 + }, + "width": 16, + "height": 16 + }, + "clicked": { + "texture": "archie:java/text_field_highlighted_dark" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/button_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/button_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..40baff1a49c5439af31b335496075e76c7578780 GIT binary patch literal 1068 zcmV+{1k?M8P)Af!-)LKzWIjPK&W4JQts>mfa5sOd&d}K(aZ0ee#}fzA@4mXYIN`Y zG>Q)D=G(TNn-N8KGUr6CHQbN3^xhX_s6EuLf%af|IkhN)(I$kjep_q31Wn(!)-IcB zdFY<5Ckle_aV&uvV=N6$*D+hsoS*@t@#@6gaKjnHgBhXn z%yiH&0ULz;rT*4IqSlJu`>C7_h~iVBp3O8$;mQDNz2AwW>f+GN=uR}X*060Gj^hAe zJ-~+?MmaiyG)Oew%ftDEW>z5{Ws~dJ-0p@oEs1E-)ES28)q!gX01fls>O7VDTpx|M zkwei)9C3^}C%(SEF2`78%cH69(KNdRKn;#&f8Q{Qw8Bsa8y$tCM(A?`o$*Ohb@+7W zc4^Pj`ND^~g$qQH(pg_w8#ivPo%Q6$o(I7Z<>xfl@nm^)meUT}r^Aw;9v(2=Z{Y%- zaUD1HJl2fu`+n&S44h_;PDNSr!1___Q=G1glA=eSqKKkLcG6+}IO#Bw4qv+D-7~IB zI-GPk>G0xIG^Npa-G`ENIO*_eeA3~h!#|RAIO%ZG;oGyEb|f86I-GQPaVqJsym$2J z`lQ2(fTY7%VVZO}aEd{oLrI5`bU5j7(&68dbU0|7Nr#gTCmmigmvlI&ecji40lNr#E1q{B&vlMY|$@ca8aK0iMfBfObWboT!f zp&mb{z214G(O6H22gv0Pt?ENKuG1+BozoiCYm^nuhHk38B>H=RasbM{@0Z!(PY16E m?uriDz#|a=3h;mb`}{9xeBQAo0=j_!0000PcciXmYIzvCzmQ3fiZPt6g$z}fh{QSHE zKtUeh#%9i|!)9i^_v!hzZPVV~`*hB>ZI|(C1JBj(kyGb+-tO=Fe!Yaq-YEcdaEx)i z6+Tl$XjJJi5k%wF0d?N*`B}WyrD##RJSdda{Wk=tqmeSc5rwzH-t6~HtAQ&RYru&{ zw}t3YowP&GQF*;L1;7<$N|?tPI;foa9?9t+<)S_0qm`p8^xXpR&Y8@J2q{bjMFbs< zQYLaj>mFr!65gr`tjO6vl}K0qFc=4KW~DBJafS?C z5tmT!aU9_r%h;Rudhb(ENroBm9(=f}@hc|w=MfW=+*ajGk)rU9^lKWhBKcBOMe9}t zn1>Oh)NqGxRu-eGp^Jn|hY4Y#RLfkJx{8h^3DS7NF(We#lD<8^rsEzk-V zY9b^?L`bQLkZ^<>?5ie1(EAmAK;{rP)kH`PuZd8riBL_1Y9jPOC5Q-;dWf8w2-QTW zCPFn4%2367H4&q dfBWC({{iO`MRY?)2YCPh002ovPDHLkV1mt0#8&_S literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/button_disabled_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/button_disabled_dark.png.mcmeta new file mode 100644 index 000000000..6d45dddfb --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/button_disabled_dark.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 64, + "height": 64, + "border": 3 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/button_highlighted_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/button_highlighted_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..369dd3eae651993965b983724e0e44e387c75844 GIT binary patch literal 1080 zcmV-81jqY{P)Mtm7IOw?KqC%19q{m@WD088d)st>AhQPZ35L=8^&v` z0~8bKWeXOvfuqQW^RkdL1Hr&PVA0Hi-NG+D68O++EayBiuIb6dz~ONmr-Q=|6RQWr zVqCz)#CqR*{R7yF2g+mEt2<3yU0RPgAV-GNQZb-BR*MOSJtopCFUCFyU@-Vl>lEqb z)$r=z9CbuWiGkl*tL2<0vf(S|JT)@@4))M@m@w}=T7sKyHv6jMRpVG0@_-E|f9smx zYg*rKH!GzK5|}8|wU>q1v+ujrS_c8%73*OxY={ASQDR0tWBT6g+>H3jV_zWL%&e4R zDJ5&IO;P1^5)=RpFVwc|4gX5SldbOQXBLjVCox7%$ph#7(-hrOyZ z0GOvhFJk;2<#rs0wbpFk_n|+SAmDvO`lb<3#t{YX+IU9y=LLtg)~PG=U9R|bvoG{KO=eDILN4hekdki)V<4m)J=!S!ldl@^+2$l;K~A&1Ao^*{VF z`5}iTFd>I~$l*((kl{R0h8*?)KdQr!!y$)54u>4(F*W2c0SP(0aLg&K6C>BQH^#13 z$YCNY9v~QQBFGRGcnwq yxkh=9mrdRQc_!e!98@&GNe$lb_mk1zvi<<9mR)j9YW$l30000?;b_hYimSxdO?47rp zV*hr6_xoMJ%5|%RZP%-O6sBp?Qf>1E=x{h_EHs2|v&mjB%hG(lQcCRm{#rKwHM@@0 nro4JYOJ=rgYaKiV`wja6!ch_X43HRy00000NkvXXu0mjftSY(0 literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/checkbox_clicked_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/checkbox_clicked_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..92c5fcd3967af52497a582e9d73c3dad6ba729dd GIT binary patch literal 364 zcmV-y0h9iTP)0CZgk z0AOYi5r{|~n7{K85tvzhYUjEx1ZxmS2^5%p)?n6_0VUABi((J6$x})iTPT!Z00h5h3eYn6-`tFX1IQDUo=f1UK zVW=OKeHR-MOVPH6%7pjY0xdKY=wuB(W6&0`n_jo3tY%oXwkf;&lvqtyz=~x%RqW6N zme2lTfO0`SHJFVdS66>&F`jU#nhdqs99WWgD0%^o3^_risL|O<{ClyDN9UgTE}x`yrq4`#-?!vnzbS^xe*r&ccJe}+E5;%K0000Q2s5Z(Y@g82^^Ju+tcVcW;a6!vzkd@9v0*Z67Cj z?n^5shWb<4cd-#M6>WQ{On9#?&_ag-?X1CP4B7&A)9cod)eNiFGG%uk607M7Sg~xU ziaj)e<+HySpj=Q-4Q64;)zx2Gj3->G219K&2bLrrie7*%LrzdBYIL>|e^0hi$vZPM zsGcuF(S1ZrlV|2NT~@N(<(-ty^qGnGeW(22+?4COl<3Fn4|cfodKzS#kpKVy07*qo IM6N<$f?;ihqW}N^ literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/energy_bar_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/energy_bar_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..51be7da7f20ad9f885c74a7e1390c793008c307e GIT binary patch literal 136 zcmeAS@N?(olHy`uVBq!ia0vp^3P3Et!3HGD8EPYel)tBoV@SoESSx!J{HkdSC^!o2UBS()_!WG8Fd=jMxRxnGr*W_|AI6h+a5B)9(GM2&9 L)z4*}Q$iB}C(o||rj^jxD5eopE=LrCa2)KKF6`6_5 zgqgw2>R->T>k8KcB$CASh#2-em%`H`@X*kDen8OY*f@Wg)aI8dUkNAX+Hr)`Up8N*5s)>*0wu*>+-A# zpB(ma8U$xU{S6%WX;Zr3)3nK(-V`PL(i~u%j1ugFd|g)s;ca1!&1||}f_+mjf3GLg z>kk7Bc_(%OcOTsAYeQiTy!0CAIP@UetiJZ~anLtUMf;6VhpcG{Zrg@`;RXEP1L&5a Rc!~f3002ovPDHLkV1kR9iiZFI literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/radio_clicked_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/radio_clicked_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..e3d3db6ed960b13401bdad5918f9b4741acb8033 GIT binary patch literal 329 zcmV-P0k-~$P)wo@lKYU+Q`dTXhMf=|;XZ#XGR;J4o9KWJwdfSBj`wvm~VPLMtj zx_+ML*I_-R!(Rh1z4|cFP^-j|Lv^pO4TUvuPKp*-n^uG9W!1IY$3bhOqIDwFp_%oU bf*0`t6T1+wv&JRg00000NkvXXu0mjfE7FR= literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/radio_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/radio_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..b6a14c4687f8327f8bf26c652535ad4fcb1973bc GIT binary patch literal 323 zcmV-J0lfZ+P)=R5cm1nIsw{mF)V?wYMhw?_8iHjKXVjbXQS88?$|7mQCapVKg>^QE$e_17$w*j+&Y-kzg7<5J(fVMF}j-Jb- z7d_p@!_>qV80p1q_T|F_=i}@yHvpij&{`Y+oVV6cwc;`s z5y(uO=ZVloO{ywH#1tbKN8QU<2Y}2(L@XP0)O~t@0|WzPs7$qdmgbB#cQAA`FUi+4 za{=WTZ;4FM<~V!GM38ZyYNQF?Wo5519yT$|%#o0pWA0YkX&DiNZQlv39ryA%a*sQq zYfz~($wl8aZ1JHU;95B=zW@%h*oJy~*}PtFY^RdBxVter{{kKjni}>Z2mbmVxZWs3 Tgo&Nw00000NkvXXu0mjfgI|Vs literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/radio_focused_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/radio_focused_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..0bdf87b679621d95c8135174800bbe5c9a7c5ecb GIT binary patch literal 328 zcmV-O0k{5%P){1U122Xy7-Ou%?%o(NTMn8PqqMZOZ56TmlziZOLu{QaiG9uBz1C8(5D2#W01Z*ao1UX3pg;Kx*Q5jzZ9E z_{cDAkU=(2c9K3@P8RM;%{JjbjcqZGyaAdW$2IsbtK{+)U>1-KEyEmx&S(SBHU`Mi zb9wZlr#pCQUi{dO*nGq9)AwG1cH+p8PWxZBwg2~ af5jUU$2fBIMfhs~0000Dsnc{pd9shhzuWsa{>XZQE_%+Aiu&d!aU{KPf(HpOfi)N_wlR_I*EHqaR5w z^t)dtEg_dcBjvS^J%@Jcd?f-$K*>Mne}kThsj`?TI+Dzk;BJj#gE*ldPA#I|WvCD!4h% z`9bii@4XSue$|zduJ5&dJ%={xNZ@nq?sRC9HJBK8?HUwAsRFWcvzn#qnPe(vrfToj z6Pu?BqMPWzNG3^+bpSP>S7n;PR9Omw#IC6=qK>J3Bj6o7rj25JsDrj#m3#t+YZH^Z zn4|=o;-G6>^0aLF!T2)f4vehO^eJIUWordjzukWK^{!reUB_F3RMn}~q2qczRTp)d z_Z=8C&~Bbv!{A&c!n7%l`PDy-O9CZjUfD$dT~aG^^B$`d@9W=MsocJ*ZA`9YquWyw zrVav}Bdxo$Z-ML?T5pGV{umUbzN)TTeBO6!*`1IO5|(tH8xJa-0MH61z^8!lB*W=rrBP;g`pNPRoYRR8{ZgB* zz;cHe9E0|Cf*>UftCj+Ks^&efD=Vw{wr!^tFeQ{DELR=Va0JY_(2pyEV~n~Q>pFHR zDtoGQ@tD`O5UypRFl8mjBD&r&5rd-%;ew|d?Vb(j504``hkm(AKv1%0XXTr!Nooxg z$nJisZm9#%lRwJrYYu=(x3+#%G8=!jog(2m;vO6&|eWnk(UK7E+^Kia7Ut9wB zd;-w1%xS^P*zGu{{SX7wvX0hG{$hb|l&3bc)pzQd#6RmT(t9 zuI1b4>5SglMjdLCV=@f}ALLnXlfG0DT&v+)nk0*fMe=Yys@*Q`>;#;ju8L_J?4ARv z-V>b(z(W>|LBfD0jq{a0PKiXIySS;pl;QCB_6))aJf@`~3|dmiHYBwIl)J8Ld=pMv z*p?-Gp3mpVP`d7_nz%soZ?&oI>?B~j!ZyR%)So07f{cDo?BC$l7`lh>mX}B{aq5<74RMTQJ%Jlj$9mZjBnD@TgX(h=$LcVv8Vfe({UX2`}-R=N#{*AqyvS? zcGaOlWc$d?ae4@rZQJbTD1o@Dta?ZWB#54y2#x+#<(G-6+Gz}_!&RSjD4~_7AGG=M zPWN#lt4_33?b&{*KRn%g>8Prsf6Ytd-Qb4lb+;ib-HDm+$e!5CfrSM{K34Z_<~l&%fM$#%HAQ9|P)}Vt~VeL03rf+ z=R8jUtlx5XBw=Ql*&7gX+b}aCqPhT*9{K}=yVp?CgJI5%UaDs-lMyjUJpehBguByP z8?V_jP1E%D&{yV=i~|EeYmMu=zI(_t2MI~leHEY&D|0D{3@9S%J(6h#*!P{|IKKJ4 i88VblvjF1HmwN$XmRoilUOMss0000v6|fK#ANmQuLyy9Zjx%<7QS zJxHo>2L$BqT-P<9A)fbXFo$A%K+qS*zTiHe3X)n|U^(Vzg?Q=4nPf70>V;skE zyGB2fTU=E(t^g(foc{%SCZ@__qUcC6Q-ZrSiVfm~ zemJ=-PF(`?JAHF&Q(;K+bpI|1!UQPe-96nQv1gL0 znCaNwttU2b45FLpz{pCH9IF6oKxZ_~V5%$yL8`8)EuxODeIwvqbxa$@_)rIJnNdE0 z!?mfByO^W|>*AnmT=JA``oZ`z<_?TxX!?|}q_UO4)o-`ob-mb2uj{xZ$Qa|+>d>FjXfF;bG=XvQcoUU7f%)rjMuhKFz+XdHwD^=vm{#xfMj52}q zVOF74rUvqrWH0&dy2dC0Ce3&420_m_rliop7Rt$Ivg&=tF(pua@=(Uv*CmsVgd4?z z?lo=c3W0qFabR(jmYiTF*BT87AIQ5?tOQ7PQXlj?e+V44j|s!)A2LP>ZsQ2-B*ovRp7hcl`M*8CHbj;P!G-Kcfb$0%$PB(|X>HO4o5x@@g117~-l zL%-Cf8CYH^46cIKb%G!z3|UJ7-LZMiD`q8|Z`*cj0aHS`g5}sT4Of5}7y2Wqkg=tlCRYcdjO2pu3Lb%}RM!RPN`orT$&Y@q92?$E|>}0-< zO;T&1Kz8>NyQL05PyQ>~mfE-P#7TDtGM81l6N+$BJ%WU3a7LsQdfA-f1j01kIukLPA>faQn0 zbHEtaL}vo(I6PjSK{$cOv^0c4OA6VBq*j1(*L96= z!f6ZJvSiP`?`?(Bbu%_`f#%<8Q`uQbz;=aghO?YG|?)cr@N*BA49GKi#`At^i8EfpZ{r{-o-j)-TKQWOtVY z?j1}zK;kp8+b4$s$KJPxbbeQ=)eWTMT+bw7AYEmm!z*Bw) zER~arg!@5c7dIwCnx7cfy-3t`v!5F|_Dq#KRc-z54l!IYb~tcrG={Xv$)Kznw}*66 zGnLa=cg&iC?SQ8(g8Rj2ciyfnGsbwH?P~xMv$4u{{|}-;XF|BZc5R*7H~W7O+D&^> zwdEQua6H~+Tp0f}N9}e+$sN3?P0mg?KgGm-cT7&ZXul3uOx|zO1$bS1!Wg)bDyZ|< y0Ym4|Hv-=6!TU}6KYEk?`T1#YzQ=p~-^YJ%)5>rBkD1;800004Tx0C=2zkv&MmKp2MKrbfqw6tAnc`2>yULJ2)x2NQvJig%&X$+}*=_-}`d+9UwF+OtZS;fTr7K zI++l&xm7XriZBKcq;*tgmN6$uDfo`Bdj$A?7vov}b$^aNHE%H>AQH!!VcNtS#50?= z!FiuJ!b-AAd`>)J(glehxvqHp#<}RSz%wIeCOuCaAr^}rtaLCdnHuplaa7fG$``U8 ztDLtuYn2*n-IKpCoYz;DxlVHgNi1Rs5=1Ddp^OS_#Aw$^v5=Klt5St1va`C500}_lx6vi~*rtpjmgE?_b{m5Jca8f%|_2u3}^g+}byFVOPGEL1I&UK}@Vf%5*E{4eTI*@((%ZFaYoO8vs>B zL=cf?QvGC#2&#(r{sUE2b2Z+<3IV+ChvPUt%rV9W)X)w}DG_6A^E}T0SZlj50krM} z5JJG1?^hqk(E>$;Wm)>00QPs&*j&#yN-g d{%47Q>I-WJa-#A7D+2%k002ovPDHLkV1nC*bh7{e literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/small_checkbox_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/small_checkbox_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..30215ba021186e02668a76148788bd0e2cbcaf17 GIT binary patch literal 196 zcmeAS@N?(olHy`uVBq!ia0vp@Ak4uAB#T}@sR2^mo-U3d6?3LeyvWO<$l-EdLi&c- ziq?PLlYZYQNW8fB$&cV?yvkf0|1^w~nPRT9I=Cosdd3GHd8^skao}#ghW1g-H3?1( zi_QHXv0VDX7;4^Ncr0y;WB#Fzidm&Aj=j+A3%DC1`t|dhM?II8U)_Dveg2}aD!1*g v@_Hq${VKgG+UvoWbD3#7erGQUu4PbF3g5Tnl;;|tvlu*G{an^LB{Ts5LzqzT literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/switch_thumb_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/switch_thumb_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..e7387d33ec3484ab0271f1682e293edf2ce45f16 GIT binary patch literal 126 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xaS5Ftmkcv5PFD>L{P~dU7$e-BZ zcQw&EGF6s?$&qPN!M~?x{FQ=sMlc3kRZdBhWpX(pR#eXL>EhK*{P!C+&j~#8?o#d( Z-l85pb9w*JWk3TNJYD@<);T3K0RTa3C<_1p literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/switch_thumb_disabled_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/switch_thumb_disabled_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..6f4eadb813f1108c1b27ccddb301a13a29571c0c GIT binary patch literal 127 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xaH%}MGkcv6UA%THE&pWUsBqk&z zT)1>e$@B8o?CT0rk`fXUb$@<1PFdzZ-_Fng1SY143Q1hGurWCl%%|fjz%6mo_X)#u X7rt)2)R&q-6B#^R{an^LB{Ts5QFx`M6r}ewZ<}PY&}ux!BDJCG&`l6jaY6Dm-JPg4afQHXIM1 zps4Rck>91!w49eFLdbfBEZ`>>;ag6MF@cIW*ADvT++7I5i#ulL&|JbU{$C(;J=8@x z6;Gdm(fOqxq(Xg=7gMsoSwiZ0;giJ~*>}fLzmB=se;VY%vxu#}yQ*(_F zyn&wC$`TNR&E%U_P}5#d9cT#B)x!^vUcqVgeiRj``g}u^2&?pEwh^Mo|*rI-}lVIcYih8+=_^MD?>j({{EN0@xT832UZmiK0IbH QbpQYW07*qoM6N<$f|{>zm;e9( literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/switch_track_clicked_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/switch_track_clicked_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..4e183785cb2bf546a7f1e224fe1c07f5af9f48f7 GIT binary patch literal 777 zcmV+k1NQuhP)fRk;P@}1VDB9mv{1yTFhGk6mUgHg8_)m^q2w4s1EG!p zT5?m1m%jJ77`=kZ19ns0X;GD8T838tKQYh#k4r36VHe3z(7S0XfrA}HkU$T(q8(T> z`Di(m1ENz+9%VtaftL5v7*EjdnlkgG17mBC7I>dco#*^+W|^pkUUAqX$-n4Lp&3A_0J0HN!lF3PER`V5TD zFa3}T^+8@t$^K>uspo|+i!-wCj-!4ZbFu##Bw;yvC#&unE0 zh{0y^O)IErucr<)1nKJG7m!}TY4v^?DJuZ8XzttvH}6=<46eRJnt9&?M(I1_x=YdX z2``cQQ|9ZD)vdC+=QS7l)phmDq}5flNN)E8RD*R41$|3hLA+p)JYW>RKidbm?b`_m z?hN&ar{R^r-UbdwbAFN(=T@^oFUqlSgZV18-x-&x__{p2H9vc1{u6%RGYdcatJ&sOMBG~$I+y-Sz8L)j2_z0b{TX}#00000NkvXX Hu0mjf>uYA! literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/switch_track_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/switch_track_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..37d8caf552920643cd0557f9af057efebff07dbf GIT binary patch literal 428 zcmV;d0aN~oP)IP#z2R9=*oKo32 zG1>RGccyDoiraP%jim8ga97vOj|5DHQ!9@-lEIjS$p5RFAo2m^tx6D*H1s2XjQ#*X WGRjwr*Tm2O0000QYM#?jp^$*Q*03)oc|L>ZtFM?Q9c&V6NNSNRZj2=DuH@;qc=1O0`ZKWE$re$AFIJaV zmQGx$_)dJ@=WWXNY=bT{&FXU^G7$rDh9oQ8qhH>z>4OYPq4JDu7@RYsd!M?&n9sq* zNDik|c1}$8{pp?Q+LYq9-AyBD`~>dmy7`d_li}3LZH^=ugD~^os%8@T0P5OSLU00004G~HC`vqHM;)SF~%5Flhc-tTwmAFP)oeLf#mmBttvV`!}< zNt$y?lGa+=*_`vTk)$z(B&q7Bra7moN>#Pt5Z8O>d1@Lcjl0D_26TeNV1iECzE5#3 zPiE@?4(>n(XF~iZhWF{8zGheYZjBFZXlGJOC)?5|gZZ4v@Ox{Q4H<-#ySmgtRc-WI zN8Vd&wc4UT3))(fly`#UV#`=vYKg&Jq}ld*b>O6$tztqQ^hj9c zi7OS~iO>7IP1&Ap&}F7seNIFsVj#|tWQBY5%NsU*kU=R_o{@~07*qoM6N<$g5Mv=R{#J2 literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..0a356bd6f354c38b014158da2399a9df73e38b5e GIT binary patch literal 155 zcmeAS@N?(olHy`uVBq!ia0vp^Qb4T0!3HFGR%fsRsU%Mq$B>FSZ!hfTJz&7Wa-n;w zXGBx4jY#xH2JQ-uOHb6zGkt{bhN?GK2pzb;X>V27z5QO_?oHQDE0bQ!#mErKdd~mt z=L>hIJ0|mVa40E7iyAGjT`!>I(jlPe)WRXC^yLYIWU|PPF11hkK-(BRUHx3vIVCg! E0R2uf+W-In literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_dark.png.mcmeta new file mode 100644 index 000000000..49e89779d --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_dark.png.mcmeta @@ -0,0 +1,15 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 26, + "height": 32, + "border": { + "left": 7, + "top": 8, + "right": 7, + "bottom": 8 + } + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_disabled_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_disabled_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..11564cd141210059b91927f316939745e93b0bb6 GIT binary patch literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^Qb4T0!3HFGR%fsRsXR{?$B>FSZ!aC>JfI-pa#4Px z(CiPpu0~8btD<|Of%k@s9-GklSBhumh>3kWXvAKyNimO|=|TKv-5vG46P8X{rfr#C zZLpn-vBS{m{}lP@OQ&Q8WcLa%Z4LW9B~i^NE&4SJr;v)r1O>+?mcPuq7#~d&Zl2Ae Q4{{QNr>mdKI;Vst060-NzW@LL literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_disabled_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_disabled_dark.png.mcmeta new file mode 100644 index 000000000..49e89779d --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_disabled_dark.png.mcmeta @@ -0,0 +1,15 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 26, + "height": 32, + "border": { + "left": 7, + "top": 8, + "right": 7, + "bottom": 8 + } + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_focused_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_focused_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..5645ebca04d01e7b92cf8d7e87c007c7d59ea5f2 GIT binary patch literal 154 zcmeAS@N?(olHy`uVBq!ia0vp^Qb4T0!3HFGR%fsRsYFi~$B>FSZ!hfTJz&7Wa-n-_ zphR<}jYxB#VZ(L>-@2m7$4oB9mI`&KGyc)Ny*mBr-_solDWFg+oxur9(i`$)9hDlE|INV|lxPmN9s``njxgN@xNA DWwtd$ literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_focused_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_focused_dark.png.mcmeta new file mode 100644 index 000000000..49e89779d --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_focused_dark.png.mcmeta @@ -0,0 +1,15 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 26, + "height": 32, + "border": { + "left": 7, + "top": 8, + "right": 7, + "bottom": 8 + } + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_selected_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_selected_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..5fb1c5dc19759e344777d9afa454cf556c170927 GIT binary patch literal 174 zcmeAS@N?(olHy`uVBq!ia0vp^Qb4T0!3HFGR%fsRsZviD$B>FSZ?A9UWmXhmy~r4= zdc|p9Y);@7S3f^di;d^R#4Qq;z3Y`!!m64$gyxGq_x_!<^LOFWt!2`dT#N?B75A@f z%|B<^@3^f)etiBM=l}*!S3j3^P6FSZ?A3SWl`jDxwt>W zdm&@g0`{naKoeKyFAkhBMUTQ%WY4dzc#P|E0dw#1uGwt})sMkhK;!FWv zjqm;M{(da+SmH{Q@-dJ1wQTw+{uS|pDjpLQ9Gh4;g}(4l2$!upcIJ=%DNE*p>T3_C dTv~Ngey6M4d2e334M0aQc)I$ztaD0e0stVSK|%lk literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_selected_highlighted_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_selected_highlighted_dark.png.mcmeta new file mode 100644 index 000000000..49e89779d --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_selected_highlighted_dark.png.mcmeta @@ -0,0 +1,15 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 26, + "height": 32, + "border": { + "left": 7, + "top": 8, + "right": 7, + "bottom": 8 + } + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..598b16dd3ebf6a0fd419f4b14a3174608ba000c0 GIT binary patch literal 182 zcmeAS@N?(olHy`uVBq!ia0vp^O+YNc!3HGF{;A#oQnj8gjv*Cu-rh14JRBgvd~xgX z9w#-9mIDSo50p8&1H6k{)0rIfzLj2IdE=AAB$b!<*=lYpUnyr$vApekW%IRNeYZbm z_6536GCy!nZc(}c3uD`YL=Gk%$HM|F5>0K290n}B&H@P>Qf&$c1Wb4w8x%8gJmcO9 gzWN;M%kYO`pB*=ElHNyaphFluUHx3vIVCg!0DyNszyJUM literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_dark.png.mcmeta new file mode 100644 index 000000000..12147d0c5 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_dark.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 130, + "height": 24, + "border": 2 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_disabled_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_disabled_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..598b16dd3ebf6a0fd419f4b14a3174608ba000c0 GIT binary patch literal 182 zcmeAS@N?(olHy`uVBq!ia0vp^O+YNc!3HGF{;A#oQnj8gjv*Cu-rh14JRBgvd~xgX z9w#-9mIDSo50p8&1H6k{)0rIfzLj2IdE=AAB$b!<*=lYpUnyr$vApekW%IRNeYZbm z_6536GCy!nZc(}c3uD`YL=Gk%$HM|F5>0K290n}B&H@P>Qf&$c1Wb4w8x%8gJmcO9 gzWN;M%kYO`pB*=ElHNyaphFluUHx3vIVCg!0DyNszyJUM literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_disabled_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_disabled_dark.png.mcmeta new file mode 100644 index 000000000..12147d0c5 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_disabled_dark.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 130, + "height": 24, + "border": 2 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_focused_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_focused_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..2cd43f7e0315195deaf1461557a6a6f916725c13 GIT binary patch literal 187 zcmeAS@N?(olHy`uVBq!ia0vp^O+YNc!3HGF{;A#o33$3VhE&XXdut=_AqNqbi-tv- zB@WCXn_Xj#%M?YNKdI^j9#AsW-<{KZ@?Eov=cVnu7PpkIY-d)foL0UfV{PfX>Bm=F zy2*OJW7z&p;4hoAKmvzUo5BGB6CTF~#SDoi2ImU~ER1an5;>T791jbyoUvu9*{A$< l{x+5VZyaAMSE(`Fd%TM$_3{4e4nWs1c)I$ztaD0e0s!1bLAC$@ literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_focused_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_focused_dark.png.mcmeta new file mode 100644 index 000000000..12147d0c5 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_focused_dark.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 130, + "height": 24, + "border": 2 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_selected_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_selected_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..eea91860f2d07317f299afae7601e84f8cbdd55f GIT binary patch literal 191 zcmeAS@N?(olHy`uVBq!ia0vp^O+YNc!3HGF{;A#oQf;0tjv*Cu-rh3gJscpwa&cp- zo1=hH_W^^Z2a^q(3cNN4oq3>?8drW-EchN<%!~K344&s+D$d$`tx9(LV_Qo%<4HCQ z-(D*I;%!qnAP_<gTe~DWM4f DU|>hA literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_selected_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_selected_dark.png.mcmeta new file mode 100644 index 000000000..12147d0c5 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_selected_dark.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 130, + "height": 24, + "border": 2 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_selected_highlighted_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_selected_highlighted_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..5a6f8c5f33a2be54d49dc14e7e3257c253317196 GIT binary patch literal 190 zcmeAS@N?(olHy`uVBq!ia0vp^O+YNc!3HGF{;A#oQmvjYjv*Cu-rm~C+Y-RTa`9)! zp}w?jOsjsLEtu<=#A#(9^w}s~+UEtM%!|)q4J!Me$!3{6|Fgb7xuh?!Uq$`EC0m#G z2^>s3jzG!RUuQF_77fS3-?#gMop9(Xj>whLeFDA$%&*SAYgEc)I$ztaD0e F0sxLO94Y_+ literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/text_field_highlighted_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/text_field_highlighted_dark.png.mcmeta new file mode 100644 index 000000000..85fa1f8a0 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/text_field_highlighted_dark.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 16, + "height": 16, + "border": 5 + } + } +} diff --git a/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/InputComponentsGameTest.kt b/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/InputComponentsGameTest.kt index 30abfffef..b644248a5 100644 --- a/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/InputComponentsGameTest.kt +++ b/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/InputComponentsGameTest.kt @@ -25,6 +25,8 @@ import net.kernelpanicsoft.archie.gui.modifiers.Modifier import net.kernelpanicsoft.archie.gui.modifiers.fillMaxSize import net.kernelpanicsoft.archie.gui.modifiers.sizeIn import net.kernelpanicsoft.archie.gui.theme.Theme +import net.kernelpanicsoft.archie.gui.theme.ThemeData +import net.kernelpanicsoft.archie.gui.theme.ThemeVariants import net.kernelpanicsoft.archie.gui.util.HsvColor import net.kernelpanicsoft.archie.gui.util.KColor import net.minecraft.network.chat.Component @@ -199,6 +201,23 @@ class InputComponentsGameTest { } } + @ClientGameTest + fun ClientGameTestContext.testDarkThemeResolvesRecoloredButtonTexture() { + // Regression check for the generated archie_themes/java/dark/*.json + *_dark.png + // assets: ThemeData.getComposableTheme looks up "java/dark/button" first when mode is + // ThemeVariants.DARK - a missing/misnamed dark file would silently fall back to the + // light "java/button" theme instead of failing loudly, so this asserts the resolved + // default-state texture is actually the recolored one. + val texture = computeOnClient { + ThemeData(ThemeVariants.DARK, "java", KColor.DARK_GRAY, KColor.WHITE) + .getComposableTheme("button") + .states[TextureStates.DEFAULT]!! + .texture + .toString() + } + assertEquals("archie:java/button_dark", texture) + } + @ClientGameTest fun ClientGameTestContext.testTextFieldTypeAndBackspace() { val typed = AtomicReference("") From 64e63cfe72f106e6d734e035a8092431da002e16 Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Wed, 12 Aug 2026 18:50:44 -0400 Subject: [PATCH 15/30] Rebuild the bedrock theme around Mojang's official bedrock-samples assets Replaces the earlier hand-spliced-from-a-CurseForge-pack version with one built primarily from Mojang/bedrock-samples' own resource_pack/textures/ui assets (used under the Minecraft EULA): classic-button/-hover/-pressed + disabledButton for button, checkboxUnFilled/checkbox_filled(+composited checkmark) for checkbox, radio_off/_on/_checked_hover for radio, common-classic_toggle_*_state for switch_track, slider_background/slider_button_default for slider/slider_handle, TabTopFront/Back for tab_menu, and background_panel for surface - each with its real nine-slice geometry translated straight from Mojang's own nineslice_size/base_size JSON sidecar into Archie's mcmeta format, rather than guessed border values. tab_game and slot are kept from the earlier CurseForge-pack splice (the user's own local files, cropped from creative_inventory/tabs.png and container/generic_54.png) since bedrock-samples' own pieces don't fit their specific shapes as well and these were already independently verified. Also gave button a real "clicked" state (classic-button-pressed), something the java theme's button never had - purely additive, no engine changes required. Full live GameTest suite: 23/23 passing. --- .../archie/archie_themes/bedrock.theme.json | 10 ++++++ .../archie/archie_themes/bedrock/button.json | 30 ++++++++++++++++++ .../archie_themes/bedrock/checkbox.json | 25 +++++++++++++++ .../archie_themes/bedrock/dark/button.json | 30 ++++++++++++++++++ .../archie_themes/bedrock/dark/checkbox.json | 25 +++++++++++++++ .../bedrock/dark/energy_bar.json | 13 ++++++++ .../bedrock/dark/fluid_tank.json | 13 ++++++++ .../bedrock/dark/progress_bar.json | 13 ++++++++ .../archie_themes/bedrock/dark/radio.json | 25 +++++++++++++++ .../archie_themes/bedrock/dark/slider.json | 19 +++++++++++ .../bedrock/dark/slider_handle.json | 19 +++++++++++ .../archie_themes/bedrock/dark/slot.json | 15 +++++++++ .../bedrock/dark/small_checkbox.json | 16 ++++++++++ .../archie_themes/bedrock/dark/surface.json | 26 +++++++++++++++ .../bedrock/dark/switch_thumb.json | 16 ++++++++++ .../bedrock/dark/switch_track.json | 25 +++++++++++++++ .../archie_themes/bedrock/dark/tab_game.json | 25 +++++++++++++++ .../archie_themes/bedrock/dark/tab_menu.json | 25 +++++++++++++++ .../bedrock/dark/text_field.json | 16 ++++++++++ .../archie_themes/bedrock/energy_bar.json | 13 ++++++++ .../archie_themes/bedrock/fluid_tank.json | 13 ++++++++ .../archie_themes/bedrock/progress_bar.json | 13 ++++++++ .../archie/archie_themes/bedrock/radio.json | 25 +++++++++++++++ .../archie/archie_themes/bedrock/slider.json | 19 +++++++++++ .../archie_themes/bedrock/slider_handle.json | 19 +++++++++++ .../archie/archie_themes/bedrock/slot.json | 15 +++++++++ .../archie_themes/bedrock/small_checkbox.json | 16 ++++++++++ .../archie/archie_themes/bedrock/surface.json | 26 +++++++++++++++ .../archie_themes/bedrock/switch_thumb.json | 16 ++++++++++ .../archie_themes/bedrock/switch_track.json | 25 +++++++++++++++ .../archie_themes/bedrock/tab_game.json | 25 +++++++++++++++ .../archie_themes/bedrock/tab_menu.json | 25 +++++++++++++++ .../archie_themes/bedrock/text_field.json | 16 ++++++++++ .../textures/gui/sprites/bedrock/button.png | Bin 0 -> 115 bytes .../gui/sprites/bedrock/button.png.mcmeta | 10 ++++++ .../gui/sprites/bedrock/button_clicked.png | Bin 0 -> 101 bytes .../sprites/bedrock/button_clicked.png.mcmeta | 10 ++++++ .../gui/sprites/bedrock/button_disabled.png | Bin 0 -> 88 bytes .../bedrock/button_disabled.png.mcmeta | 10 ++++++ .../sprites/bedrock/button_highlighted.png | Bin 0 -> 108 bytes .../bedrock/button_highlighted.png.mcmeta | 10 ++++++ .../textures/gui/sprites/bedrock/checkbox.png | Bin 0 -> 138 bytes .../gui/sprites/bedrock/checkbox_clicked.png | Bin 0 -> 230 bytes .../bedrock/checkbox_clicked_and_focused.png | Bin 0 -> 230 bytes .../gui/sprites/bedrock/checkbox_disabled.png | Bin 0 -> 164 bytes .../gui/sprites/bedrock/checkbox_focused.png | Bin 0 -> 138 bytes .../gui/sprites/bedrock/energy_bar.png | Bin 0 -> 78 bytes .../gui/sprites/bedrock/energy_bar.png.mcmeta | 10 ++++++ .../gui/sprites/bedrock/fluid_tank.png | Bin 0 -> 78 bytes .../gui/sprites/bedrock/fluid_tank.png.mcmeta | 10 ++++++ .../gui/sprites/bedrock/progress_bar.png | Bin 0 -> 78 bytes .../sprites/bedrock/progress_bar.png.mcmeta | 10 ++++++ .../textures/gui/sprites/bedrock/radio.png | Bin 0 -> 101 bytes .../gui/sprites/bedrock/radio_clicked.png | Bin 0 -> 134 bytes .../bedrock/radio_clicked_and_focused.png | Bin 0 -> 135 bytes .../gui/sprites/bedrock/radio_disabled.png | Bin 0 -> 101 bytes .../gui/sprites/bedrock/radio_focused.png | Bin 0 -> 146 bytes .../textures/gui/sprites/bedrock/slider.png | Bin 0 -> 78 bytes .../gui/sprites/bedrock/slider.png.mcmeta | 10 ++++++ .../gui/sprites/bedrock/slider_handle.png | Bin 0 -> 115 bytes .../sprites/bedrock/slider_handle.png.mcmeta | 10 ++++++ .../bedrock/slider_handle_highlighted.png | Bin 0 -> 122 bytes .../slider_handle_highlighted.png.mcmeta | 10 ++++++ .../sprites/bedrock/slider_highlighted.png | Bin 0 -> 78 bytes .../bedrock/slider_highlighted.png.mcmeta | 10 ++++++ .../textures/gui/sprites/bedrock/slot.png | Bin 0 -> 507 bytes .../gui/sprites/bedrock/slot_dark.png | Bin 0 -> 511 bytes .../gui/sprites/bedrock/small_checkbox.png | Bin 0 -> 136 bytes .../bedrock/small_checkbox_clicked.png | Bin 0 -> 219 bytes .../textures/gui/sprites/bedrock/surface.png | Bin 0 -> 159 bytes .../gui/sprites/bedrock/surface.png.mcmeta | 10 ++++++ .../gui/sprites/bedrock/surface_dark.png | Bin 0 -> 162 bytes .../sprites/bedrock/surface_dark.png.mcmeta | 10 ++++++ .../gui/sprites/bedrock/surface_inset.png | Bin 0 -> 507 bytes .../sprites/bedrock/surface_inset.png.mcmeta | 10 ++++++ .../sprites/bedrock/surface_inset_dark.png | Bin 0 -> 511 bytes .../bedrock/surface_inset_dark.png.mcmeta | 10 ++++++ .../gui/sprites/bedrock/switch_thumb.png | Bin 0 -> 104 bytes .../sprites/bedrock/switch_thumb_disabled.png | Bin 0 -> 104 bytes .../gui/sprites/bedrock/switch_track.png | Bin 0 -> 322 bytes .../sprites/bedrock/switch_track.png.mcmeta | 10 ++++++ .../sprites/bedrock/switch_track_clicked.png | Bin 0 -> 289 bytes .../bedrock/switch_track_clicked.png.mcmeta | 10 ++++++ .../switch_track_clicked_and_focused.png | Bin 0 -> 273 bytes ...witch_track_clicked_and_focused.png.mcmeta | 10 ++++++ .../sprites/bedrock/switch_track_disabled.png | Bin 0 -> 311 bytes .../bedrock/switch_track_disabled.png.mcmeta | 10 ++++++ .../sprites/bedrock/switch_track_focused.png | Bin 0 -> 325 bytes .../bedrock/switch_track_focused.png.mcmeta | 10 ++++++ .../textures/gui/sprites/bedrock/tab_game.png | Bin 0 -> 538 bytes .../gui/sprites/bedrock/tab_game.png.mcmeta | 15 +++++++++ .../gui/sprites/bedrock/tab_game_disabled.png | Bin 0 -> 133 bytes .../bedrock/tab_game_disabled.png.mcmeta | 15 +++++++++ .../gui/sprites/bedrock/tab_game_focused.png | Bin 0 -> 138 bytes .../bedrock/tab_game_focused.png.mcmeta | 15 +++++++++ .../gui/sprites/bedrock/tab_game_selected.png | Bin 0 -> 560 bytes .../bedrock/tab_game_selected.png.mcmeta | 15 +++++++++ .../bedrock/tab_game_selected_highlighted.png | Bin 0 -> 160 bytes .../tab_game_selected_highlighted.png.mcmeta | 15 +++++++++ .../textures/gui/sprites/bedrock/tab_menu.png | Bin 0 -> 154 bytes .../gui/sprites/bedrock/tab_menu.png.mcmeta | 10 ++++++ .../gui/sprites/bedrock/tab_menu_disabled.png | Bin 0 -> 162 bytes .../bedrock/tab_menu_disabled.png.mcmeta | 10 ++++++ .../gui/sprites/bedrock/tab_menu_focused.png | Bin 0 -> 180 bytes .../bedrock/tab_menu_focused.png.mcmeta | 10 ++++++ .../gui/sprites/bedrock/tab_menu_selected.png | Bin 0 -> 144 bytes .../bedrock/tab_menu_selected.png.mcmeta | 10 ++++++ .../bedrock/tab_menu_selected_highlighted.png | Bin 0 -> 144 bytes .../tab_menu_selected_highlighted.png.mcmeta | 10 ++++++ .../gui/sprites/bedrock/text_field.png | Bin 0 -> 78 bytes .../gui/sprites/bedrock/text_field.png.mcmeta | 10 ++++++ .../bedrock/text_field_highlighted.png | Bin 0 -> 78 bytes .../bedrock/text_field_highlighted.png.mcmeta | 10 ++++++ .../internal/tests/InputComponentsGameTest.kt | 19 +++++++++++ 114 files changed, 1016 insertions(+) create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock.theme.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/button.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/checkbox.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/button.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/checkbox.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/energy_bar.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/fluid_tank.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/progress_bar.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/radio.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_handle.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slot.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/small_checkbox.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/surface.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_thumb.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/tab_game.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/tab_menu.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/text_field.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/energy_bar.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/fluid_tank.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/progress_bar.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/radio.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider_handle.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/slot.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/small_checkbox.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/surface.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_thumb.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_track.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/tab_game.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/tab_menu.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/text_field.json create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_clicked.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_clicked.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_disabled.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_disabled.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_clicked.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_clicked_and_focused.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_disabled.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_focused.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/energy_bar.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/energy_bar.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/fluid_tank.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/fluid_tank.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/progress_bar.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/progress_bar.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio_clicked.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio_clicked_and_focused.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio_disabled.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio_focused.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_handle.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_handle.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_handle_highlighted.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_handle_highlighted.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_highlighted.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_highlighted.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slot.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slot_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/small_checkbox.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/small_checkbox_clicked.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface_inset.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface_inset.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface_inset_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface_inset_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb_disabled.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_and_focused.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_and_focused.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_disabled.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_disabled.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_focused.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_focused.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game_disabled.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game_disabled.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game_focused.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game_focused.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game_selected.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game_selected.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game_selected_highlighted.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game_selected_highlighted.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_disabled.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_disabled.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_focused.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_focused.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_selected.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_selected.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_selected_highlighted.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_selected_highlighted.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/text_field.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/text_field.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/text_field_highlighted.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/text_field_highlighted.png.mcmeta diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock.theme.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock.theme.json new file mode 100644 index 000000000..8bf76d3b2 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock.theme.json @@ -0,0 +1,10 @@ +{ + "variants": [ + "", + "dark" + ], + "default_variant": "", + "aliases": { + "default": "" + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/button.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/button.json new file mode 100644 index 000000000..e8b5b19f7 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/button.json @@ -0,0 +1,30 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/button", + "texture_size": { + "width": 5, + "height": 5 + }, + "width": 5, + "height": 5 + }, + "focused": { + "texture": "archie:bedrock/button_highlighted" + }, + "clicked": { + "texture": "archie:bedrock/button_clicked" + }, + "disabled": { + "texture": "archie:bedrock/button_disabled" + } + }, + "min_size": { + "width": 50, + "height": 20 + }, + "content_padding": { + "horizontal": 4, + "vertical": 2 + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/checkbox.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/checkbox.json new file mode 100644 index 000000000..c7c89d17e --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/checkbox.json @@ -0,0 +1,25 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/checkbox", + "texture_size": { + "width": 16, + "height": 13 + }, + "width": 16, + "height": 13 + }, + "focused": { + "texture": "archie:bedrock/checkbox_focused" + }, + "clicked": { + "texture": "archie:bedrock/checkbox_clicked" + }, + "clicked_and_focused": { + "texture": "archie:bedrock/checkbox_clicked_and_focused" + }, + "disabled": { + "texture": "archie:bedrock/checkbox_disabled" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/button.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/button.json new file mode 100644 index 000000000..e8b5b19f7 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/button.json @@ -0,0 +1,30 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/button", + "texture_size": { + "width": 5, + "height": 5 + }, + "width": 5, + "height": 5 + }, + "focused": { + "texture": "archie:bedrock/button_highlighted" + }, + "clicked": { + "texture": "archie:bedrock/button_clicked" + }, + "disabled": { + "texture": "archie:bedrock/button_disabled" + } + }, + "min_size": { + "width": 50, + "height": 20 + }, + "content_padding": { + "horizontal": 4, + "vertical": 2 + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/checkbox.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/checkbox.json new file mode 100644 index 000000000..c7c89d17e --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/checkbox.json @@ -0,0 +1,25 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/checkbox", + "texture_size": { + "width": 16, + "height": 13 + }, + "width": 16, + "height": 13 + }, + "focused": { + "texture": "archie:bedrock/checkbox_focused" + }, + "clicked": { + "texture": "archie:bedrock/checkbox_clicked" + }, + "clicked_and_focused": { + "texture": "archie:bedrock/checkbox_clicked_and_focused" + }, + "disabled": { + "texture": "archie:bedrock/checkbox_disabled" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/energy_bar.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/energy_bar.json new file mode 100644 index 000000000..b9713458d --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/energy_bar.json @@ -0,0 +1,13 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/energy_bar", + "texture_size": { + "width": 32, + "height": 16 + }, + "width": 32, + "height": 16 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/fluid_tank.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/fluid_tank.json new file mode 100644 index 000000000..c6759b82e --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/fluid_tank.json @@ -0,0 +1,13 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/fluid_tank", + "texture_size": { + "width": 18, + "height": 54 + }, + "width": 18, + "height": 54 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/progress_bar.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/progress_bar.json new file mode 100644 index 000000000..73a7826c4 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/progress_bar.json @@ -0,0 +1,13 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/progress_bar", + "texture_size": { + "width": 32, + "height": 16 + }, + "width": 32, + "height": 16 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/radio.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/radio.json new file mode 100644 index 000000000..e4cd76815 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/radio.json @@ -0,0 +1,25 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/radio", + "texture_size": { + "width": 10, + "height": 10 + }, + "width": 10, + "height": 10 + }, + "focused": { + "texture": "archie:bedrock/radio_focused" + }, + "clicked": { + "texture": "archie:bedrock/radio_clicked" + }, + "clicked_and_focused": { + "texture": "archie:bedrock/radio_clicked_and_focused" + }, + "disabled": { + "texture": "archie:bedrock/radio_disabled" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider.json new file mode 100644 index 000000000..b1c96aff6 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider.json @@ -0,0 +1,19 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/slider", + "texture_size": { + "width": 3, + "height": 3 + }, + "width": 200, + "height": 20 + }, + "focused": { + "texture": "archie:bedrock/slider_highlighted" + }, + "clicked": { + "texture": "archie:bedrock/slider_highlighted" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_handle.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_handle.json new file mode 100644 index 000000000..1d04b1ed1 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_handle.json @@ -0,0 +1,19 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/slider_handle", + "texture_size": { + "width": 6, + "height": 6 + }, + "width": 8, + "height": 20 + }, + "focused": { + "texture": "archie:bedrock/slider_handle_highlighted" + }, + "clicked": { + "texture": "archie:bedrock/slider_handle_highlighted" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slot.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slot.json new file mode 100644 index 000000000..9aab7c25e --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slot.json @@ -0,0 +1,15 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/slot_dark", + "texture_size": { + "width": 18, + "height": 18 + }, + "width": 18, + "height": 18, + "uWidth": 18, + "vHeight": 18 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/small_checkbox.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/small_checkbox.json new file mode 100644 index 000000000..2fa339c3e --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/small_checkbox.json @@ -0,0 +1,16 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/small_checkbox", + "texture_size": { + "width": 13, + "height": 13 + }, + "width": 13, + "height": 13 + }, + "clicked": { + "texture": "archie:bedrock/small_checkbox_clicked" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/surface.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/surface.json new file mode 100644 index 000000000..5b7e16be7 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/surface.json @@ -0,0 +1,26 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/surface_dark", + "texture_size": { + "width": 16, + "height": 16 + }, + "width": 16, + "height": 16 + } + }, + "variants": { + "inset": { + "default": { + "texture": "archie:bedrock/surface_inset_dark", + "texture_size": { + "width": 18, + "height": 18 + }, + "width": 16, + "height": 16 + } + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_thumb.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_thumb.json new file mode 100644 index 000000000..d1438deb8 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_thumb.json @@ -0,0 +1,16 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/switch_thumb", + "texture_size": { + "width": 14, + "height": 14 + }, + "width": 14, + "height": 14 + }, + "disabled": { + "texture": "archie:bedrock/switch_thumb_disabled" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json new file mode 100644 index 000000000..135fdccc8 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json @@ -0,0 +1,25 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/switch_track", + "texture_size": { + "width": 38, + "height": 20 + }, + "width": 38, + "height": 20 + }, + "focused": { + "texture": "archie:bedrock/switch_track_focused" + }, + "clicked": { + "texture": "archie:bedrock/switch_track_clicked" + }, + "clicked_and_focused": { + "texture": "archie:bedrock/switch_track_clicked_and_focused" + }, + "disabled": { + "texture": "archie:bedrock/switch_track_disabled" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/tab_game.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/tab_game.json new file mode 100644 index 000000000..d2e6b534e --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/tab_game.json @@ -0,0 +1,25 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/tab_game", + "texture_size": { + "width": 26, + "height": 32 + }, + "width": 26, + "height": 32 + }, + "focused": { + "texture": "archie:bedrock/tab_game_focused" + }, + "clicked": { + "texture": "archie:bedrock/tab_game_selected" + }, + "clicked_and_focused": { + "texture": "archie:bedrock/tab_game_selected_highlighted" + }, + "disabled": { + "texture": "archie:bedrock/tab_game_disabled" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/tab_menu.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/tab_menu.json new file mode 100644 index 000000000..6b9ae8171 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/tab_menu.json @@ -0,0 +1,25 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/tab_menu", + "texture_size": { + "width": 12, + "height": 11 + }, + "width": 130, + "height": 24 + }, + "focused": { + "texture": "archie:bedrock/tab_menu_focused" + }, + "clicked": { + "texture": "archie:bedrock/tab_menu_selected" + }, + "clicked_and_focused": { + "texture": "archie:bedrock/tab_menu_selected_highlighted" + }, + "disabled": { + "texture": "archie:bedrock/tab_menu_disabled" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/text_field.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/text_field.json new file mode 100644 index 000000000..f528b3a8b --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/text_field.json @@ -0,0 +1,16 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/text_field", + "texture_size": { + "width": 16, + "height": 16 + }, + "width": 16, + "height": 16 + }, + "clicked": { + "texture": "archie:bedrock/text_field_highlighted" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/energy_bar.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/energy_bar.json new file mode 100644 index 000000000..b9713458d --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/energy_bar.json @@ -0,0 +1,13 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/energy_bar", + "texture_size": { + "width": 32, + "height": 16 + }, + "width": 32, + "height": 16 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/fluid_tank.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/fluid_tank.json new file mode 100644 index 000000000..c6759b82e --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/fluid_tank.json @@ -0,0 +1,13 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/fluid_tank", + "texture_size": { + "width": 18, + "height": 54 + }, + "width": 18, + "height": 54 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/progress_bar.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/progress_bar.json new file mode 100644 index 000000000..73a7826c4 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/progress_bar.json @@ -0,0 +1,13 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/progress_bar", + "texture_size": { + "width": 32, + "height": 16 + }, + "width": 32, + "height": 16 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/radio.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/radio.json new file mode 100644 index 000000000..e4cd76815 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/radio.json @@ -0,0 +1,25 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/radio", + "texture_size": { + "width": 10, + "height": 10 + }, + "width": 10, + "height": 10 + }, + "focused": { + "texture": "archie:bedrock/radio_focused" + }, + "clicked": { + "texture": "archie:bedrock/radio_clicked" + }, + "clicked_and_focused": { + "texture": "archie:bedrock/radio_clicked_and_focused" + }, + "disabled": { + "texture": "archie:bedrock/radio_disabled" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider.json new file mode 100644 index 000000000..b1c96aff6 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider.json @@ -0,0 +1,19 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/slider", + "texture_size": { + "width": 3, + "height": 3 + }, + "width": 200, + "height": 20 + }, + "focused": { + "texture": "archie:bedrock/slider_highlighted" + }, + "clicked": { + "texture": "archie:bedrock/slider_highlighted" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider_handle.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider_handle.json new file mode 100644 index 000000000..1d04b1ed1 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider_handle.json @@ -0,0 +1,19 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/slider_handle", + "texture_size": { + "width": 6, + "height": 6 + }, + "width": 8, + "height": 20 + }, + "focused": { + "texture": "archie:bedrock/slider_handle_highlighted" + }, + "clicked": { + "texture": "archie:bedrock/slider_handle_highlighted" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slot.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slot.json new file mode 100644 index 000000000..484eab7e3 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slot.json @@ -0,0 +1,15 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/slot", + "texture_size": { + "width": 18, + "height": 18 + }, + "width": 18, + "height": 18, + "uWidth": 18, + "vHeight": 18 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/small_checkbox.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/small_checkbox.json new file mode 100644 index 000000000..2fa339c3e --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/small_checkbox.json @@ -0,0 +1,16 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/small_checkbox", + "texture_size": { + "width": 13, + "height": 13 + }, + "width": 13, + "height": 13 + }, + "clicked": { + "texture": "archie:bedrock/small_checkbox_clicked" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/surface.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/surface.json new file mode 100644 index 000000000..8380ca4a6 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/surface.json @@ -0,0 +1,26 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/surface", + "texture_size": { + "width": 16, + "height": 16 + }, + "width": 16, + "height": 16 + } + }, + "variants": { + "inset": { + "default": { + "texture": "archie:bedrock/surface_inset", + "texture_size": { + "width": 18, + "height": 18 + }, + "width": 16, + "height": 16 + } + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_thumb.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_thumb.json new file mode 100644 index 000000000..d1438deb8 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_thumb.json @@ -0,0 +1,16 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/switch_thumb", + "texture_size": { + "width": 14, + "height": 14 + }, + "width": 14, + "height": 14 + }, + "disabled": { + "texture": "archie:bedrock/switch_thumb_disabled" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_track.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_track.json new file mode 100644 index 000000000..135fdccc8 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_track.json @@ -0,0 +1,25 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/switch_track", + "texture_size": { + "width": 38, + "height": 20 + }, + "width": 38, + "height": 20 + }, + "focused": { + "texture": "archie:bedrock/switch_track_focused" + }, + "clicked": { + "texture": "archie:bedrock/switch_track_clicked" + }, + "clicked_and_focused": { + "texture": "archie:bedrock/switch_track_clicked_and_focused" + }, + "disabled": { + "texture": "archie:bedrock/switch_track_disabled" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/tab_game.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/tab_game.json new file mode 100644 index 000000000..d2e6b534e --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/tab_game.json @@ -0,0 +1,25 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/tab_game", + "texture_size": { + "width": 26, + "height": 32 + }, + "width": 26, + "height": 32 + }, + "focused": { + "texture": "archie:bedrock/tab_game_focused" + }, + "clicked": { + "texture": "archie:bedrock/tab_game_selected" + }, + "clicked_and_focused": { + "texture": "archie:bedrock/tab_game_selected_highlighted" + }, + "disabled": { + "texture": "archie:bedrock/tab_game_disabled" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/tab_menu.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/tab_menu.json new file mode 100644 index 000000000..6b9ae8171 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/tab_menu.json @@ -0,0 +1,25 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/tab_menu", + "texture_size": { + "width": 12, + "height": 11 + }, + "width": 130, + "height": 24 + }, + "focused": { + "texture": "archie:bedrock/tab_menu_focused" + }, + "clicked": { + "texture": "archie:bedrock/tab_menu_selected" + }, + "clicked_and_focused": { + "texture": "archie:bedrock/tab_menu_selected_highlighted" + }, + "disabled": { + "texture": "archie:bedrock/tab_menu_disabled" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/text_field.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/text_field.json new file mode 100644 index 000000000..f528b3a8b --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/text_field.json @@ -0,0 +1,16 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/text_field", + "texture_size": { + "width": 16, + "height": 16 + }, + "width": 16, + "height": 16 + }, + "clicked": { + "texture": "archie:bedrock/text_field_highlighted" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button.png new file mode 100644 index 0000000000000000000000000000000000000000..6e73eea20d6efe7eafe81cb9ffe0929b8970771a GIT binary patch literal 115 zcmeAS@N?(olHy`uVBq!ia0vp^tRT$61|)m))t&+=D^C~4kcv6ECpPjnCkcv6UR|2m8I^V#u=S_}e zL;{b*hYw{*2}uW-HgIjKxOLO0qQZJ12NOf)Zq~ZO6OogE`WQT2{an^LB{Ts5Tr?l6 literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_clicked.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_clicked.png.mcmeta new file mode 100644 index 000000000..a41901d7c --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_clicked.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 5, + "height": 5, + "border": 2 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_disabled.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..3d161a9d8cf64c1329616125fe7c646dda99137a GIT binary patch literal 88 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~@}4e^Ar*6yMa0DZoo8V6;Q<0q gPfrlV({sXz!H=7Hwz_-FWuQ_9Pgg&ebxsLQ0KETPpwIeUsAzSQvMO@jX?k&hY?hXYh3Ob6Mw< G&;$VaOCbLM literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted.png.mcmeta new file mode 100644 index 000000000..a41901d7c --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 5, + "height": 5, + "border": 2 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox.png new file mode 100644 index 0000000000000000000000000000000000000000..4512346625af9f88d95856730577483ed316aec0 GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^0w6XA8<1SE`<)7q3iNbw45^rt{OA9FduG*!PNlm% zJUlwOy1Fjb3zscx6Fkho#>UphDXb=NB_<%C;V!E|+q7w7erGRDx@7TGgReP|dBwyD l2|P7{t``_$I$IPb1{>+a! zx^?T;o)<|vJU}3<=A*D;=Yjb&=lAdbVt4+BY#R?qNQdt|y6WPord~xYLypyH zBFqV0h77lz*x1^7c{Zn?Pjt9`<^T}1=q*^F*?C-f2E*Y7_UrSLC5ql0DQvrPMdT5W zgSm_2&ZdK>4o#{MUm*SIK_0u`Ssosb#*Ea1Y`g|4&hH*w?apFQS!E`4t??{FgpwjV a0|Qfr{>EoMMst7;XYh3Ob6Mw<&;$T+J6SgX literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_clicked_and_focused.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_clicked_and_focused.png new file mode 100644 index 0000000000000000000000000000000000000000..53feb42ca872055cdcb987646aeec8f141094322 GIT binary patch literal 230 zcmeAS@N?(olHy`uVBq!ia0vp^0w6XA8<1SE`<)7qTI=cJ7*a7OIYA=hz>oLZx9gKc zjSK%LxriqxCL|;XOnt1n;^)`J=XpS&sOZywdD|)u^A6TyH+HlSv1$3fUG& zFIo_DFd;S3a}W0g-pYdQ%*)f**p@hK*_6=CCNW8|{NpQEQwE_>Y0lRU%p%KO7(`zf V^USsBe+P6pgQu&X%Q~loCIGtvA literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_disabled.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..2f7825668d4661a875b1d9a5c08ea7a1269b1d8d GIT binary patch literal 164 zcmeAS@N?(olHy`uVBq!ia0vp^0w6XA8<1SE`<)7q%Jy_|45^rtoFI{L;KqduALd6K z<>lq=d6A^U0|feUdlXhgIyo_|w&3nrxa=4EYNj|jw>InY)&i+BO=liHJy@W~%)Ho$ znfY+_95#c8Qxt`Gn3*3mo;ZD&gV(@_x4}j9-GyV>4C`E+co-P06!~^nr-_CEt!40Z L^>bP0l+XkK(M>dg literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_focused.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_focused.png new file mode 100644 index 0000000000000000000000000000000000000000..146e3b2c10ac826b7dada021b529e6b8894fe51b GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^0w6XA8<1SE`<)7q3iNbw45^rt{OA9FduG*!PNlm% zJUlv|pPf~{#p&tk*(1Sb00bdnVM15#+`HFzm(`$c<3>Zjvs^`77Ed+!ngf|vOq`Iw jQxoWVfgz@|MS_8WMT3|7%M2+?py3Rju6{1-oD!Mf4>3F(0hE&W+PDx1kao&L?Au%B# zp@XCQ!0L>nt3-Kxj?6fA;DCXXsF9I?f4`FgrIhE&Yyz2L}oz<|T$;`~e2 zZ2lG%Uc0+Y0&-*Q(^A}ASlG*%Z)_1edGzaa6>$gtr>C|F?F&348o#i`O!uSstySXd h#BS8RNt0UMY|lJfnK$CKY9G*G22WQ%mvv4FO#lb~EBXKc literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio_clicked_and_focused.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio_clicked_and_focused.png new file mode 100644 index 0000000000000000000000000000000000000000..22d1fb3d789d33586fbb2114e523ff4718e448f5 GIT binary patch literal 135 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4`FXlHhE&Yyz2L}oz<|T$;`~dq z*=(*fq;FmA!F=`D*JQUe7ngzp^9JASi7_$to<)pb0@medKa^ID`6%j@aapK-;o7Q) jS&eUx=PfI=`oVmCjxz851BokuCNp@t`njxgN@xNA`eQJ2 literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio_disabled.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..188ae44eb73d81e2e7a665d7b54968a1b10e1c29 GIT binary patch literal 101 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4>3F(0hE&W+PDx1kao&L?Au%B# yp`fJT$m)#$jemK3j?6fA;DCXXsF9I?a4f4MS8k8hE&YiI>DRkfC7)p{G-`V zr6wtR^*{9MN}shZie+ia<;bU>jd(m8I5e0mKKieiC(2M+xb@nW$>QP*{=|H}C$4T> v{aSg_dwGNNXZ4;l^=0dvuAIhU_lM1IqfUG5+ZDTjmN0m_`njxgN@xNAA)qwr literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider.png new file mode 100644 index 0000000000000000000000000000000000000000..58ad1b6a0d5d31848cb96be77745c5d15d40a2d0 GIT binary patch literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~qMj~}Ar*6yO9~2poM&M5;qhQ- aWoEFqWITGsl2;6M z!AHAnVj7-bv2U~e*smyLz{tkN*2X-2z5y5{8nfkzoL^aAFxB8d$LS)5=iQ?7mF+}o Qfrc@7y85}Sb4q9e0J(D~#Q*>R literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_handle_highlighted.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_handle_highlighted.png.mcmeta new file mode 100644 index 000000000..f89033b90 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_handle_highlighted.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 6, + "height": 6, + "border": 2 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_highlighted.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_highlighted.png new file mode 100644 index 0000000000000000000000000000000000000000..d8cb86221ba67c6ef85ca4bc7d112269cd76699e GIT binary patch literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~qMj~}Ar*6yQ}oh)oM&M5;qhQ- aWo9txVyv#XAH4#ofWgz%&t;ucLK6T4Tx0C=2zkv&MmKpe$iQ>8^JA{G&G$WWauh>AE$6^me@v=v%)FuC*#nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|=H{g6A|?JWDYS_3;J6>}?mh0_0Yam~RI_UWP&La) z#baVNw<-o+5kx-*5k^pArk+SIX5cx#?&0I>U6f~epZjz4DtVIuK9P8i>4rtTK|Hf* z>74h8!>lAJ#OK8023?T&k?XR{Z=8z`3p_JyWK#3QVPdh^!Ey()lA#h$6Gs$PqkJLj zvch?bvs$UK);;+PLwRi_&2^e1h+_!}Bq2gZ4P{hdAxf)8iis5M$2|PQjz38*nOtQs zax9<<6_Voz|AXJ%nuV!JHz^bYx?gPjV-yJN0?oQ@e;?a+^91le16NwxUu^)hpQP8@ zTI2}m+XgPK+nT%wT zj1?(+-Q(TC&ffk#)9UXBNPKdZqDo_Q0000oNkl4Tx0C=2zkv&MmKpe$iQ>8^JA{G&G$WWauh>AE$6^me@v=v%)FuC*#nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|=H{g6A|?JWDYS_3;J6>}?mh0_0Yam~RI_UWP&La) z#baVNw<-o+5kx-*5k^pArk+SIX5cx#?&0I>U6f~epZjz4DtVIuK9P8i>4rtTK|Hf* z>74h8!>lAJ#OK8023?T&k?XR{Z=8z`3p_JyWK#3QVPdh^!Ey()lA#h$6Gs$PqkJLj zvch?bvs$UK);;+PLwRi_&2^e1h+_!}Bq2gZ4P{hdAxf)8iis5M$2|PQjz38*nOtQs zax9<<6_Voz|AXJ%nuV!JHz^bYx?gPjV-yJN0?oQ@e;?a+^91le16NwxUu^)hpQP8@ zTI2}m+XgPK+nT%wT zj1?(+-Q(TC&ffk#)9UXBNPKdZqDo_Q0000sNkl;DABmA(lB(TwGiRxlM*!wrnv_S*p5pqm;|E`EI6;j5a3< iS@r#VZzu`KGcY`y#nXGp6QoH%*%fBlyiipnv<%*@P(kA>z3ibO|lFmzF6OU!V% zn38(1EsOQG6B}DwFVE)m^N9}E4uC+*nZOHEj=c1lGfOU}zHiaT8=a4NR<6{Pm=$q> zRX|ExVvUXt=k1m?9}nK;n{&?j#*u9T%a!KLTC~V~(lSo5#v@D&-79n&mzc_Y1iF^N M)78&qol`;+05rl=&j0`b literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface.png new file mode 100644 index 0000000000000000000000000000000000000000..4f6757e4fa850b98068b4a4dff4be5f7fbc6fe13 GIT binary patch literal 159 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`X`U{QAr*7pP7dU1FyL{%pECWH zpQiQE4+?iTK5FN9CGh;4s)UA!mvc}Mdk3RWwerfHXWqChlAAG;;pgflL8lhKFGh|#yj35CYbHIAP-5VZZgAqbx*^iB=VK|?=ShqOJo`R#t*R){lUY`G7HA`br>mdK II;Vst0EOB+ssI20 literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface.png.mcmeta new file mode 100644 index 000000000..827cb1139 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 16, + "height": 16, + "border": 4 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..75833a21c1e5677de32ad616617ea0a37a96e8c9 GIT binary patch literal 162 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`nVv3=Ar*7pPTt6SKtX_+e+p}z zTv)=}_{Y(w4R4h!pC?Y-y?7PFkx0XajtN1#)HCi@ z8*D!Hc+HQ_8}&bV?J_P*<2a|b%UbD^6`y**)t6$^f*3e-&i!O?<5E&7IH<%3w3NZq L)z4*}Q$iB}kuEvb literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface_dark.png.mcmeta new file mode 100644 index 000000000..827cb1139 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface_dark.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 16, + "height": 16, + "border": 4 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface_inset.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface_inset.png new file mode 100644 index 0000000000000000000000000000000000000000..c020602e1d868babd4108a7ac873782eb139a836 GIT binary patch literal 507 zcmV4Tx0C=2zkv&MmKpe$iQ>8^JA{G&G$WWauh>AE$6^me@v=v%)FuC*#nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|=H{g6A|?JWDYS_3;J6>}?mh0_0Yam~RI_UWP&La) z#baVNw<-o+5kx-*5k^pArk+SIX5cx#?&0I>U6f~epZjz4DtVIuK9P8i>4rtTK|Hf* z>74h8!>lAJ#OK8023?T&k?XR{Z=8z`3p_JyWK#3QVPdh^!Ey()lA#h$6Gs$PqkJLj zvch?bvs$UK);;+PLwRi_&2^e1h+_!}Bq2gZ4P{hdAxf)8iis5M$2|PQjz38*nOtQs zax9<<6_Voz|AXJ%nuV!JHz^bYx?gPjV-yJN0?oQ@e;?a+^91le16NwxUu^)hpQP8@ zTI2}m+XgPK+nT%wT zj1?(+-Q(TC&ffk#)9UXBNPKdZqDo_Q0000oNkl4Tx0C=2zkv&MmKpe$iQ>8^JA{G&G$WWauh>AE$6^me@v=v%)FuC*#nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|=H{g6A|?JWDYS_3;J6>}?mh0_0Yam~RI_UWP&La) z#baVNw<-o+5kx-*5k^pArk+SIX5cx#?&0I>U6f~epZjz4DtVIuK9P8i>4rtTK|Hf* z>74h8!>lAJ#OK8023?T&k?XR{Z=8z`3p_JyWK#3QVPdh^!Ey()lA#h$6Gs$PqkJLj zvch?bvs$UK);;+PLwRi_&2^e1h+_!}Bq2gZ4P{hdAxf)8iis5M$2|PQjz38*nOtQs zax9<<6_Voz|AXJ%nuV!JHz^bYx?gPjV-yJN0?oQ@e;?a+^91le16NwxUu^)hpQP8@ zTI2}m+XgPK+nT%wT zj1?(+-Q(TC&ffk#)9UXBNPKdZqDo_Q0000sNkl B9GL(B literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track.png new file mode 100644 index 0000000000000000000000000000000000000000..777213803f004417674269a4b7c6667d4fd7c2a0 GIT binary patch literal 322 zcmV-I0lof-P)y zF%E(-6o!8d7jR$^Jc1er77yX#rk=>n#RHk>!k~i2$%$5n4z5T{a4;xM1X?IU;$SR4NsR6QfrKz!553t+$C8Aw;N0+O)OAq#zx`fUdW`W{ln7QJPxcjDG~dLz!5T zR8|(z%v@0sKM|uB26|yqHUB-aJd}1S;}~sSx~8@hi_@%07*qoM6N<$g3Sqs;{X5v literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track.png.mcmeta new file mode 100644 index 000000000..fff81e9a3 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 38, + "height": 20, + "border": 3 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked.png new file mode 100644 index 0000000000000000000000000000000000000000..8076ca0156a40a0c944a88ef45d8d005bcd43751 GIT binary patch literal 289 zcmeAS@N?(olHy`uVBq!ia0vp^YCtT)!3HEB_WN@HsTZCujv*Cul6(3(|D88DabVI> zZ;42s(ywf{WHlaqdFssr0y-e#xZixafa3=mrmobSbCxMtBqcTQs*tz4`=l%F?QM%b zL>8=YW?X*3=?c%u^c@GLhwDpL968cibmiXJYJ(#)Ixd|&a9~1w*fAh>TT|4Aasc{~S=PbW5zMNE}NOqD+rD{JZ2npdrw z)XNV5moSuGGzL;)zkERj%K?_-Rh7NHcUE+Zoq|Al`IHbqJU4WKG>4gP7@TW&Spxvx XD;hAV+4R{U00000NkvXXu0mjfiC=1n literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_and_focused.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_and_focused.png.mcmeta new file mode 100644 index 000000000..fff81e9a3 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_and_focused.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 38, + "height": 20, + "border": 3 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_disabled.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..4f9ecbbb5f2cb7fbf25f66a27956b1cd276a6bfc GIT binary patch literal 311 zcmV-70m%M|P)y z%L;-(6o!ApeG&QsT|jz=h`?SpqF3-Cf)cvZa!#UK6KWxr138X5rqH*OGymb^s57J4 z3;T%{q65HqFl^d`V}ClvVgRsp-NFqRV=zrqsno=>0N^7A0Q1=tqmkV74u>O_tF=<8 zG@KaSJ?Y?Pv&*b8#-w_PrmbgIDqy zF%E(-6o!8dM{vMJh-c8mg~?kuIpPf*oxGEYP7Io$#?gtiIMBfziHSHElqLc#RwVJ; z$lv$!k*b_TO4bcF=>h*u^!QpJ(hZq2?97nnVAq1vrs&vB8vH;*E1_1VWg#JKm zdhW)>bT(Him4*|eCkDV3DqJkrnHeEOs7Kzkwany0x`qK=Z@tYJJHw+ix4xPFh@z`9 zu`H>gETLJr;zv{>M*RTP52R}Tdtzny+Nn%qblTE2wVfCz7RTcE`3?DN89SHC627*T z9;Jmp(L6Y@FuY)QI0_Ypmnbt+aj|n^oBj6VhN^gqCC?2h%G3y=0gs1S-Dt?1#45Q1 Xl$lB_Y7c0&00000NkvXXu0mjfqR@=u literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_focused.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_focused.png.mcmeta new file mode 100644 index 000000000..fff81e9a3 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_focused.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 38, + "height": 20, + "border": 3 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game.png new file mode 100644 index 0000000000000000000000000000000000000000..2ce51c5b2baef9d1a2d55472ed3cf9eff6701748 GIT binary patch literal 538 zcmV+#0_FXQP)4Tx0C=2zkv&MmKpe$iQ>8^JA{G&G$WWauh>AE$6^me@v=v%)FuC*#nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|=H{g6A|?JWDYS_3;J6>}?mh0_0Yam~RI_UWP&La) z#baVNw<-o+5kx-*5k^pArk+SIX5cx#?&0I>U6f~epZjz4DtVIuK9P8i>4rtTK|Hf* z>74h8!>lAJ#OK8023?T&k?XR{Z=8z`3p_JyWK#3QVPdh^!Ey()lA#h$6Gs$PqkJLj zvch?bvs$UK);;+PLwRi_&2^e1h+_!}Bq2gZ4P{hdAxf)8iis5M$2|PQjz38*nOtQs zax9<<6_Voz|AXJ%nuV!JHz^bYx?gPjV-yJN0?oQ@e;?a+^91le16NwxUu^)hpQP8@ zTI2}m+XgPK+nT%wT zj1?(+-Q(TC&ffk#)9UXBNPKdZqDo_Q0000{NklB(8|lf-(B9 zg3*9EEx3q4D*YGj7O=0&d3tEj05C6W7*OlYv2Py)aFsIz8dCzG54ry&BqSsxBqSsx cBqY?P7b)QiY{nz~U;qFB07*qoM6N<$g29dE4FCWD literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game.png.mcmeta new file mode 100644 index 000000000..49e89779d --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game.png.mcmeta @@ -0,0 +1,15 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 26, + "height": 32, + "border": { + "left": 7, + "top": 8, + "right": 7, + "bottom": 8 + } + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game_disabled.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..fa6d9624077949fefffca9edfce2d5816f2ee87d GIT binary patch literal 133 zcmeAS@N?(olHy`uVBq!ia0vp^Qb4T0!3HFGR%fsRDIZT4$B>FSZ_gTvHW&yTxNsoZ zg0-v1 Zmv3z6N}LFSZ_jSzY%maExETCK zCHaUZOJNPG`-BHV%dQ-{_HFqt=?8JsKYzKffH5KNn!$o=vy<=JaXipU=P+5u;vir2 f-_rvQR`}{~?dK}}A6pv&G@QZH)z4*}Q$iB}W5F+= literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game_focused.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game_focused.png.mcmeta new file mode 100644 index 000000000..49e89779d --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game_focused.png.mcmeta @@ -0,0 +1,15 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 26, + "height": 32, + "border": { + "left": 7, + "top": 8, + "right": 7, + "bottom": 8 + } + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game_selected.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..e20e01bd0ae1ade44c5997cd8f0bf783e60bb739 GIT binary patch literal 560 zcmV-00?+-4P)4Tx0C=2zkv&MmKpe$iQ>8^JA{G&G$WWauh>AE$6^me@v=v%)FuC*#nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|=H{g6A|?JWDYS_3;J6>}?mh0_0Yam~RI_UWP&La) z#baVNw<-o+5kx-*5k^pArk+SIX5cx#?&0I>U6f~epZjz4DtVIuK9P8i>4rtTK|Hf* z>74h8!>lAJ#OK8023?T&k?XR{Z=8z`3p_JyWK#3QVPdh^!Ey()lA#h$6Gs$PqkJLj zvch?bvs$UK);;+PLwRi_&2^e1h+_!}Bq2gZ4P{hdAxf)8iis5M$2|PQjz38*nOtQs zax9<<6_Voz|AXJ%nuV!JHz^bYx?gPjV-yJN0?oQ@e;?a+^91le16NwxUu^)hpQP8@ zTI2}m+XgPK+nT%wT zj1?(+-Q(TC&ffk#)9UXBNPKdZqDo_Q0001INklFS$svJ(f6qIxA2@X2 zzySf1!(yvbmVb_!#G`$Gc8{Xk5#>V$>S?w|l-StV+PwSECun?%S>Qj(^+tolei3F6 t5I(9V$mymlwKe0)qQGu6hSDzvhHOLbjtCCX-9RfDJYD@<);T3K0RY88GXMYp literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game_selected_highlighted.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game_selected_highlighted.png.mcmeta new file mode 100644 index 000000000..49e89779d --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_game_selected_highlighted.png.mcmeta @@ -0,0 +1,15 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 26, + "height": 32, + "border": { + "left": 7, + "top": 8, + "right": 7, + "bottom": 8 + } + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..952025bb82accd70383be1f3ae83b6498bb50e0d GIT binary patch literal 154 zcmeAS@N?(olHy`uVBq!ia0vp^JV4CN!3HF~3v%LtRHCPgV@SoEr4tWw9x>oyNk6;w z#i~1<(uO-$p7rL`kdRG@V>+v((#c-zwsS-6H`T;whP9Izk4bnK9nd$w9<~0!z1jDy zC*9GqyzKS3SYg|L_S;>YsTt=g=bKuzIk5$NZ9c;)P@=Ns*)9z+pk)l6u6{1-oD!M< DQ9U+2 literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu.png.mcmeta new file mode 100644 index 000000000..e0f56a513 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 12, + "height": 11, + "border": 4 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_disabled.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..81f4a32d6a215dce9ebe472592c0ef73c92a83f6 GIT binary patch literal 162 zcmeAS@N?(olHy`uVBq!ia0vp^JV4CN!3HF~3v%LtRHmnkV@SoEr4uf4Ix7mWxOT;4aa!`e8ph@rh0mo0n`O3l@^@Rml$rK+wJzsvP6c-(hiqO??gyoM$0gz$ zE!DS%sjQVbscvcVkMGU<`h#V+#193$|GToZm^W9lFUni<;ZpMgBl!vMUQG-GTFT(* L>gTe~DWM4fR0lXk literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_disabled.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_disabled.png.mcmeta new file mode 100644 index 000000000..e0f56a513 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_disabled.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 12, + "height": 11, + "border": 4 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_focused.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_focused.png new file mode 100644 index 0000000000000000000000000000000000000000..88cec945b2a4c4aa5ac758d98fe652174f1a17a0 GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^JV4CN!3HF~3v%LtRJEszV@SoEy%P=j92`Vk(#^Ej zEx2eg$!&Qt*V^b8B~o)Qc}|fk59nzURm`>y{+n`7#lG^0q-T@5%FDdh4_2M8wNW|Q z>%+m;uw~nI_HU7XpMOZ@a8H~fsB*I9LAuX`AN3BCat&DyZoTa=NzCGiN6@t+^9>k2 f#Qn&*@B6-Hli@y|;`C0SGZ;Kw{an^LB{Ts5w~IsZ literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_focused.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_focused.png.mcmeta new file mode 100644 index 000000000..e0f56a513 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_focused.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 12, + "height": 11, + "border": 4 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_selected.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..ae5b08170ad53ff1f57b37446bdd96af19704823 GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^JV4CN!3HF~3v%LtRJf;$V@SoEtrHG%9x&iJQnK)I zN=wcRS)<<7(8A5T-G@yGywo!>^6`9 literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_selected.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_selected.png.mcmeta new file mode 100644 index 000000000..e0f56a513 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_selected.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 12, + "height": 11, + "border": 4 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_selected_highlighted.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_selected_highlighted.png new file mode 100644 index 0000000000000000000000000000000000000000..cc47406e3f93ccb2e9ce9df46c9e4196c4f5ddff GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^JV4CN!3HF~3v%LtRJf;$V@SoEtrHG%9x&iJQnIi* zrCn#1;HGZ#lPcHK6;;13UK_{eEuo`#Ms98M^EuJGuXW`zPVr%rNLDz>aD3OBz4x0s t7Hn-(neG#(x$>jgguh?T^6UMPpR?t%#K~~xW;LJ{44$rjF6*2UngE--G&leN literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_selected_highlighted.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_selected_highlighted.png.mcmeta new file mode 100644 index 000000000..e0f56a513 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_selected_highlighted.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 12, + "height": 11, + "border": 4 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/text_field.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/text_field.png new file mode 100644 index 0000000000000000000000000000000000000000..58ad1b6a0d5d31848cb96be77745c5d15d40a2d0 GIT binary patch literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~qMj~}Ar*6yO9~2poM&M5;qhQ- aWoEFqWITGsl2;6 Date: Wed, 12 Aug 2026 19:04:52 -0400 Subject: [PATCH 16/30] Wire real text_edit_base/hover assets for text_field, fix meter-frame mcmeta text_field previously reused slider_background's recessed look since nothing dedicated seemed to exist - bedrock-samples actually has text_edit_base.png/text_edit_hover.png, a real sunken text-input tile. No nineslice json ships alongside it, but it still needs to stretch to arbitrary field widths (matching what java's own text_field.json required), so it's declared nine-slice anyway with a conservative border derived from its visible banding, rather than left non-nineslice - a non-nineslice sprite renders at its own tiny native size regardless of the caller's node size. Also fixed a real bug this surfaced while auditing the pattern: the energy_bar/progress_bar/fluid_tank mcmeta files declared the desired render size (32x16 etc.) instead of slider_background.png's actual 3x3 source dimensions, which would have broken nine-slice UV mapping. mcmeta must describe the source image; the render size belongs only in the theme JSON's own width/height, which blitSprite scales the nine-slice source to independently. Full live GameTest suite: 23/23 passing. --- .../archie_themes/bedrock/dark/energy_bar.json | 4 ++-- .../archie_themes/bedrock/dark/fluid_tank.json | 4 ++-- .../archie_themes/bedrock/dark/progress_bar.json | 4 ++-- .../archie_themes/bedrock/dark/text_field.json | 4 ++-- .../archie/archie_themes/bedrock/energy_bar.json | 4 ++-- .../archie/archie_themes/bedrock/fluid_tank.json | 4 ++-- .../archie_themes/bedrock/progress_bar.json | 4 ++-- .../archie/archie_themes/bedrock/text_field.json | 4 ++-- .../gui/sprites/bedrock/energy_bar.png.mcmeta | 6 +++--- .../gui/sprites/bedrock/fluid_tank.png.mcmeta | 6 +++--- .../gui/sprites/bedrock/progress_bar.png.mcmeta | 6 +++--- .../textures/gui/sprites/bedrock/text_field.png | Bin 78 -> 133 bytes .../gui/sprites/bedrock/text_field.png.mcmeta | 6 +++--- .../sprites/bedrock/text_field_highlighted.png | Bin 78 -> 124 bytes .../bedrock/text_field_highlighted.png.mcmeta | 6 +++--- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/energy_bar.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/energy_bar.json index b9713458d..c2193bd3d 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/energy_bar.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/energy_bar.json @@ -3,8 +3,8 @@ "default": { "texture": "archie:bedrock/energy_bar", "texture_size": { - "width": 32, - "height": 16 + "width": 3, + "height": 3 }, "width": 32, "height": 16 diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/fluid_tank.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/fluid_tank.json index c6759b82e..7aa18dd4a 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/fluid_tank.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/fluid_tank.json @@ -3,8 +3,8 @@ "default": { "texture": "archie:bedrock/fluid_tank", "texture_size": { - "width": 18, - "height": 54 + "width": 3, + "height": 3 }, "width": 18, "height": 54 diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/progress_bar.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/progress_bar.json index 73a7826c4..12f09b408 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/progress_bar.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/progress_bar.json @@ -3,8 +3,8 @@ "default": { "texture": "archie:bedrock/progress_bar", "texture_size": { - "width": 32, - "height": 16 + "width": 3, + "height": 3 }, "width": 32, "height": 16 diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/text_field.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/text_field.json index f528b3a8b..ed3bc8e7b 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/text_field.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/text_field.json @@ -3,8 +3,8 @@ "default": { "texture": "archie:bedrock/text_field", "texture_size": { - "width": 16, - "height": 16 + "width": 8, + "height": 8 }, "width": 16, "height": 16 diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/energy_bar.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/energy_bar.json index b9713458d..c2193bd3d 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/energy_bar.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/energy_bar.json @@ -3,8 +3,8 @@ "default": { "texture": "archie:bedrock/energy_bar", "texture_size": { - "width": 32, - "height": 16 + "width": 3, + "height": 3 }, "width": 32, "height": 16 diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/fluid_tank.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/fluid_tank.json index c6759b82e..7aa18dd4a 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/fluid_tank.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/fluid_tank.json @@ -3,8 +3,8 @@ "default": { "texture": "archie:bedrock/fluid_tank", "texture_size": { - "width": 18, - "height": 54 + "width": 3, + "height": 3 }, "width": 18, "height": 54 diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/progress_bar.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/progress_bar.json index 73a7826c4..12f09b408 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/progress_bar.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/progress_bar.json @@ -3,8 +3,8 @@ "default": { "texture": "archie:bedrock/progress_bar", "texture_size": { - "width": 32, - "height": 16 + "width": 3, + "height": 3 }, "width": 32, "height": 16 diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/text_field.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/text_field.json index f528b3a8b..ed3bc8e7b 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/text_field.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/text_field.json @@ -3,8 +3,8 @@ "default": { "texture": "archie:bedrock/text_field", "texture_size": { - "width": 16, - "height": 16 + "width": 8, + "height": 8 }, "width": 16, "height": 16 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/energy_bar.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/energy_bar.png.mcmeta index 3655fa44b..5630bc1ac 100644 --- a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/energy_bar.png.mcmeta +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/energy_bar.png.mcmeta @@ -2,9 +2,9 @@ "gui": { "scaling": { "type": "nine_slice", - "width": 32, - "height": 16, - "border": 2 + "width": 3, + "height": 3, + "border": 1 } } } diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/fluid_tank.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/fluid_tank.png.mcmeta index 5df3d98be..5630bc1ac 100644 --- a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/fluid_tank.png.mcmeta +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/fluid_tank.png.mcmeta @@ -2,9 +2,9 @@ "gui": { "scaling": { "type": "nine_slice", - "width": 18, - "height": 54, - "border": 2 + "width": 3, + "height": 3, + "border": 1 } } } diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/progress_bar.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/progress_bar.png.mcmeta index 3655fa44b..5630bc1ac 100644 --- a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/progress_bar.png.mcmeta +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/progress_bar.png.mcmeta @@ -2,9 +2,9 @@ "gui": { "scaling": { "type": "nine_slice", - "width": 32, - "height": 16, - "border": 2 + "width": 3, + "height": 3, + "border": 1 } } } diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/text_field.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/text_field.png index 58ad1b6a0d5d31848cb96be77745c5d15d40a2d0..393d8bc88603343bc2f7de2a87be205fa32de816 100644 GIT binary patch literal 133 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqKAtX)Ar*6a`y9CrCuz!~T%YvE&_U3!_nnxD)AolbN-2Aa#@>FVdQ&MBb@036XQNB{r; literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~qMj~}Ar*6yO9~2poM&M5;qhQ- aWoEFqWITGsl2;6*m{L`+r6}Y2F>M@SsDsdS06mHj(M-&zjTJ{ XA9=#pw>+2$G>*a3)z4*}Q$iB}jy5Vc literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~qMj~}Ar*6yQ}oh)oM&M5;qhQ- aWo9txVyv#XAH4#ofWgz%&t;ucLK6T Date: Wed, 12 Aug 2026 19:25:38 -0400 Subject: [PATCH 17/30] Correct button/checkbox/radio bedrock assets against verified UI templates button: "classic-button" turned out to be the old/legacy style. ui_template_buttons.json's own dark_text_button/light_text_button definitions reference button_borderless_dark/light (+hover/pressed) instead - switched to those, and gave the light and dark theme variants their own distinct button set (not just a recolored surface) since the real templates pair each with its own hover/pressed states. checkbox/radio: ui_common.json's own "checkbox"/"radio_toggle" node definitions reveal both share the exact same unchecked-state art (checkbox_space/checkbox_spaceHover) and differ only in their checked state - checkbox_check (checkbox) vs checkbox_filled (radio_toggle). Previously had this backwards: checkbox was using checkbox_filled/ checkboxUnFilled, and radio was using radio_off/radio_on/ radio_checked_hover, none of which ui_common.json's actual checkbox/ radio_toggle definitions ever reference. switch: found the real toggle_off/toggle_on(+hover) asset pair - a proper thumb+track graphic with "O"/"I" labels, replacing the more abstract common-classic_toggle_*_state used before. Split into separate track and thumb crops since Archie always composites those as two independent textures. text_field: wired the real text_edit_base/hover asset (a proper sunken input tile) instead of reusing the slider background. Also fixed two invalid nine-slice configs surfaced by NeoForge's sprite loader actually parsing these at boot (a class of bug none of the earlier verification could have caught without a real client run): disabledButton(NoBorder).json's own nineslice_size doesn't fit its tiny base_size in either case (2*border >= size, and border=0 isn't accepted either - "Value must be positive") - declared non-nineslice instead, resized up to a fixed reasonable size. Fixed an unrelated meter-frame bug too: their mcmeta declared the desired render size instead of the actual 3x3 source dimensions, which would have broken nine-slice UV mapping. Full live GameTest suite: 23/23 passing, zero sprite metadata parse errors (previously 4). --- .../archie/archie_themes/bedrock/button.json | 16 ++++++++---- .../archie_themes/bedrock/checkbox.json | 8 +++--- .../archie_themes/bedrock/dark/button.json | 22 ++++++++++------ .../archie_themes/bedrock/dark/checkbox.json | 8 +++--- .../bedrock/dark/switch_thumb.json | 8 +++--- .../bedrock/dark/switch_track.json | 24 +++++++++++++----- .../archie_themes/bedrock/switch_thumb.json | 8 +++--- .../archie_themes/bedrock/switch_track.json | 24 +++++++++++++----- .../textures/gui/sprites/bedrock/button.png | Bin 115 -> 108 bytes .../gui/sprites/bedrock/button.png.mcmeta | 6 ++--- .../gui/sprites/bedrock/button_clicked.png | Bin 101 -> 101 bytes .../sprites/bedrock/button_clicked.png.mcmeta | 6 ++--- .../sprites/bedrock/button_clicked_dark.png | Bin 0 -> 100 bytes ....mcmeta => button_clicked_dark.png.mcmeta} | 6 ++--- .../gui/sprites/bedrock/button_dark.png | Bin 0 -> 103 bytes ...rack.png.mcmeta => button_dark.png.mcmeta} | 6 ++--- .../gui/sprites/bedrock/button_disabled.png | Bin 88 -> 112 bytes .../sprites/bedrock/button_disabled_dark.png | Bin 0 -> 112 bytes .../sprites/bedrock/button_highlighted.png | Bin 108 -> 102 bytes .../bedrock/button_highlighted.png.mcmeta | 6 ++--- .../bedrock/button_highlighted_dark.png | Bin 0 -> 104 bytes ...eta => button_highlighted_dark.png.mcmeta} | 6 ++--- .../textures/gui/sprites/bedrock/checkbox.png | Bin 138 -> 101 bytes .../gui/sprites/bedrock/checkbox_clicked.png | Bin 230 -> 123 bytes .../bedrock/checkbox_clicked_and_focused.png | Bin 230 -> 134 bytes .../gui/sprites/bedrock/checkbox_disabled.png | Bin 164 -> 101 bytes .../gui/sprites/bedrock/checkbox_focused.png | Bin 138 -> 101 bytes .../gui/sprites/bedrock/radio_clicked.png | Bin 134 -> 146 bytes .../bedrock/radio_clicked_and_focused.png | Bin 135 -> 147 bytes .../gui/sprites/bedrock/radio_disabled.png | Bin 101 -> 150 bytes .../gui/sprites/bedrock/radio_focused.png | Bin 146 -> 101 bytes .../gui/sprites/bedrock/small_checkbox.png | Bin 136 -> 104 bytes .../bedrock/small_checkbox_clicked.png | Bin 219 -> 139 bytes .../gui/sprites/bedrock/switch_thumb.png | Bin 104 -> 500 bytes .../sprites/bedrock/switch_thumb_disabled.png | Bin 104 -> 99 bytes .../gui/sprites/bedrock/switch_track.png | Bin 322 -> 542 bytes .../sprites/bedrock/switch_track_clicked.png | Bin 289 -> 117 bytes .../switch_track_clicked_and_focused.png | Bin 273 -> 116 bytes ...witch_track_clicked_and_focused.png.mcmeta | 10 -------- .../sprites/bedrock/switch_track_disabled.png | Bin 311 -> 141 bytes .../bedrock/switch_track_disabled.png.mcmeta | 10 -------- .../sprites/bedrock/switch_track_focused.png | Bin 325 -> 547 bytes .../bedrock/switch_track_focused.png.mcmeta | 10 -------- 43 files changed, 95 insertions(+), 89 deletions(-) create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_clicked_dark.png rename core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/{button_disabled.png.mcmeta => button_clicked_dark.png.mcmeta} (56%) create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_dark.png rename core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/{switch_track.png.mcmeta => button_dark.png.mcmeta} (55%) create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_disabled_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted_dark.png rename core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/{switch_track_clicked.png.mcmeta => button_highlighted_dark.png.mcmeta} (55%) delete mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_and_focused.png.mcmeta delete mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_disabled.png.mcmeta delete mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_focused.png.mcmeta diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/button.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/button.json index e8b5b19f7..dff0b9dd9 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/button.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/button.json @@ -3,11 +3,11 @@ "default": { "texture": "archie:bedrock/button", "texture_size": { - "width": 5, - "height": 5 + "width": 4, + "height": 4 }, - "width": 5, - "height": 5 + "width": 4, + "height": 4 }, "focused": { "texture": "archie:bedrock/button_highlighted" @@ -16,7 +16,13 @@ "texture": "archie:bedrock/button_clicked" }, "disabled": { - "texture": "archie:bedrock/button_disabled" + "texture": "archie:bedrock/button_disabled", + "texture_size": { + "width": 50, + "height": 20 + }, + "width": 50, + "height": 20 } }, "min_size": { diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/checkbox.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/checkbox.json index c7c89d17e..1dd19cea5 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/checkbox.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/checkbox.json @@ -3,11 +3,11 @@ "default": { "texture": "archie:bedrock/checkbox", "texture_size": { - "width": 16, - "height": 13 + "width": 10, + "height": 10 }, - "width": 16, - "height": 13 + "width": 10, + "height": 10 }, "focused": { "texture": "archie:bedrock/checkbox_focused" diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/button.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/button.json index e8b5b19f7..d00d982f6 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/button.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/button.json @@ -1,22 +1,28 @@ { "states": { "default": { - "texture": "archie:bedrock/button", + "texture": "archie:bedrock/button_dark", "texture_size": { - "width": 5, - "height": 5 + "width": 4, + "height": 4 }, - "width": 5, - "height": 5 + "width": 4, + "height": 4 }, "focused": { - "texture": "archie:bedrock/button_highlighted" + "texture": "archie:bedrock/button_highlighted_dark" }, "clicked": { - "texture": "archie:bedrock/button_clicked" + "texture": "archie:bedrock/button_clicked_dark" }, "disabled": { - "texture": "archie:bedrock/button_disabled" + "texture": "archie:bedrock/button_disabled_dark", + "texture_size": { + "width": 50, + "height": 20 + }, + "width": 50, + "height": 20 } }, "min_size": { diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/checkbox.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/checkbox.json index c7c89d17e..1dd19cea5 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/checkbox.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/checkbox.json @@ -3,11 +3,11 @@ "default": { "texture": "archie:bedrock/checkbox", "texture_size": { - "width": 16, - "height": 13 + "width": 10, + "height": 10 }, - "width": 16, - "height": 13 + "width": 10, + "height": 10 }, "focused": { "texture": "archie:bedrock/checkbox_focused" diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_thumb.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_thumb.json index d1438deb8..3e70e0c46 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_thumb.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_thumb.json @@ -3,11 +3,11 @@ "default": { "texture": "archie:bedrock/switch_thumb", "texture_size": { - "width": 14, - "height": 14 + "width": 10, + "height": 12 }, - "width": 14, - "height": 14 + "width": 10, + "height": 12 }, "disabled": { "texture": "archie:bedrock/switch_thumb_disabled" diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json index 135fdccc8..9799705f5 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json @@ -3,20 +3,32 @@ "default": { "texture": "archie:bedrock/switch_track", "texture_size": { - "width": 38, - "height": 20 + "width": 17, + "height": 12 }, - "width": 38, - "height": 20 + "width": 17, + "height": 12 }, "focused": { "texture": "archie:bedrock/switch_track_focused" }, "clicked": { - "texture": "archie:bedrock/switch_track_clicked" + "texture": "archie:bedrock/switch_track_clicked", + "texture_size": { + "width": 16, + "height": 12 + }, + "width": 16, + "height": 12 }, "clicked_and_focused": { - "texture": "archie:bedrock/switch_track_clicked_and_focused" + "texture": "archie:bedrock/switch_track_clicked_and_focused", + "texture_size": { + "width": 16, + "height": 12 + }, + "width": 16, + "height": 12 }, "disabled": { "texture": "archie:bedrock/switch_track_disabled" diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_thumb.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_thumb.json index d1438deb8..3e70e0c46 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_thumb.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_thumb.json @@ -3,11 +3,11 @@ "default": { "texture": "archie:bedrock/switch_thumb", "texture_size": { - "width": 14, - "height": 14 + "width": 10, + "height": 12 }, - "width": 14, - "height": 14 + "width": 10, + "height": 12 }, "disabled": { "texture": "archie:bedrock/switch_thumb_disabled" diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_track.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_track.json index 135fdccc8..9799705f5 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_track.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_track.json @@ -3,20 +3,32 @@ "default": { "texture": "archie:bedrock/switch_track", "texture_size": { - "width": 38, - "height": 20 + "width": 17, + "height": 12 }, - "width": 38, - "height": 20 + "width": 17, + "height": 12 }, "focused": { "texture": "archie:bedrock/switch_track_focused" }, "clicked": { - "texture": "archie:bedrock/switch_track_clicked" + "texture": "archie:bedrock/switch_track_clicked", + "texture_size": { + "width": 16, + "height": 12 + }, + "width": 16, + "height": 12 }, "clicked_and_focused": { - "texture": "archie:bedrock/switch_track_clicked_and_focused" + "texture": "archie:bedrock/switch_track_clicked_and_focused", + "texture_size": { + "width": 16, + "height": 12 + }, + "width": 16, + "height": 12 }, "disabled": { "texture": "archie:bedrock/switch_track_disabled" diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button.png index 6e73eea20d6efe7eafe81cb9ffe0929b8970771a..f8e193fded4693e329ce937cacee70217229a5aa 100644 GIT binary patch literal 108 zcmeAS@N?(olHy`uVBq!ia0vp^EFjFm1|(O0oL2{=j6Gc(Ln`LP9^5F{V8Fp};QHx* z(Hp#1NE|aU$$0bP0 Hl+XkKu=6Gl literal 115 zcmeAS@N?(olHy`uVBq!ia0vp^tRT$61|)m))t&+=D^C~4kcv6ECpPjnCkcv6UR|2m8I^V#u=S_}e zL;{b*hYw{*2}uW-HgIjKxOLO0qQZJ12NOf)Zq~ZO6OogE`WQT2{an^LB{Ts5Tr?l6 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_clicked.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_clicked.png.mcmeta index a41901d7c..eeca6296f 100644 --- a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_clicked.png.mcmeta +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_clicked.png.mcmeta @@ -2,9 +2,9 @@ "gui": { "scaling": { "type": "nine_slice", - "width": 5, - "height": 5, - "border": 2 + "width": 4, + "height": 4, + "border": 1 } } } diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_clicked_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_clicked_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..46189b22a6aedf1017e6a3ee9db080dd77a6c620 GIT binary patch literal 100 zcmeAS@N?(olHy`uVBq!ia0vp^EFjFm1|(O0oL2{=v^`xMLn`JZr*tIzJkP*4o=;^t9pk@AcH5vhO^ItCvEuqh{39pe>H3Ss(V1444$rjF6*2UngDTPpwIeUsAzSQvMO@jX?k&hY?hXYh3Ob6Mw< G&;$VaOCbLM diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted.png.mcmeta index a41901d7c..eeca6296f 100644 --- a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted.png.mcmeta +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted.png.mcmeta @@ -2,9 +2,9 @@ "gui": { "scaling": { "type": "nine_slice", - "width": 5, - "height": 5, - "border": 2 + "width": 4, + "height": 4, + "border": 1 } } } diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..181a28dea070ee5c5bfee204535747c86ae2e7a3 GIT binary patch literal 104 zcmeAS@N?(olHy`uVBq!ia0vp^EFjFm1|(O0oL2{=^gUf1Ln`JZ^F#^!n$O5}E%x4l z1BVVAIItn_ZB++{IWw~`vs6NgM%v%=A4Jx5Gcrsx;{5Qb>%eZHRt8U3KbLh*2~7aW C-XWp@ literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted_dark.png.mcmeta similarity index 55% rename from core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked.png.mcmeta rename to core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted_dark.png.mcmeta index fff81e9a3..eeca6296f 100644 --- a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked.png.mcmeta +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted_dark.png.mcmeta @@ -2,9 +2,9 @@ "gui": { "scaling": { "type": "nine_slice", - "width": 38, - "height": 20, - "border": 3 + "width": 4, + "height": 4, + "border": 1 } } } diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox.png index 4512346625af9f88d95856730577483ed316aec0..af36f4434687a99531fc9ce74e8c34b84e5f1337 100644 GIT binary patch literal 101 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4>3F(0hE&W+PDx1kao&L?Au%B# zp@XCQ!0L>nt3-Kxj?6fA;DCXXsF9I?UphDXb=NB_<%C;V!E|+q7w7erGRDx@7TGgReP|dBwyD l2|P7{t``_$I$If4IeEG`hE&W+{`3F;|9Unh24Tal zj-VGW9&jZ7InFHeM)pC@9IlC?2SpcgFZvwl)8d&F+>&Uj=L%H)|NM{t_EQfFGW;z% V;K^)Oa~^0KgQu&X%Q~loCICkCEhYc} literal 230 zcmeAS@N?(olHy`uVBq!ia0vp^0w6XA8<1SE`<)7qTI=cJ7*a7OIYA=hz>Pb1{>+a! zx^?T;o)<|vJU}3<=A*D;=Yjb&=lAdbVt4+BY#R?qNQdt|y6WPord~xYLypyH zBFqV0h77lz*x1^7c{Zn?Pjt9`<^T}1=q*^F*?C-f2E*Y7_UrSLC5ql0DQvrPMdT5W zgSm_2&ZdK>4o#{MUm*SIK_0u`Ssosb#*Ea1Y`g|4&hH*w?apFQS!E`4t??{FgpwjV a0|Qfr{>EoMMst7;XYh3Ob6Mw<&;$T+J6SgX diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_clicked_and_focused.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_clicked_and_focused.png index 53feb42ca872055cdcb987646aeec8f141094322..65bd43d724e951e9f8d2819e43f4a13f66ea5670 100644 GIT binary patch literal 134 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4`FgrIhE&Yyz2L}oz<|T$;`~e2 zZ2lG%Uc0+Y0&-*Q(^A}ASlG*%Z)_1edGzaa6>$gtr>C|F?F&348o#i`O!uSstySXd h#BS8RNt0UMY|lJfnK$CKY9G*G22WQ%mvv4FO#lb~EBXKc literal 230 zcmeAS@N?(olHy`uVBq!ia0vp^0w6XA8<1SE`<)7qTI=cJ7*a7OIYA=hz>oLZx9gKc zjSK%LxriqxCL|;XOnt1n;^)`J=XpS&sOZywdD|)u^A6TyH+HlSv1$3fUG& zFIo_DFd;S3a}W0g-pYdQ%*)f**p@hK*_6=CCNW8|{NpQEQwE_>Y0lRU%p%KO7(`zf V^USsBe+P6pgQu&X%Q~loCIGtvA diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_disabled.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_disabled.png index 2f7825668d4661a875b1d9a5c08ea7a1269b1d8d..af36f4434687a99531fc9ce74e8c34b84e5f1337 100644 GIT binary patch literal 101 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4>3F(0hE&W+PDx1kao&L?Au%B# zp@XCQ!0L>nt3-Kxj?6fA;DCXXsF9I?lq=d6A^U0|feUdlXhgIyo_|w&3nrxa=4EYNj|jw>InY)&i+BO=liHJy@W~%)Ho$ znfY+_95#c8Qxt`Gn3*3mo;ZD&gV(@_x4}j9-GyV>4C`E+co-P06!~^nr-_CEt!40Z L^>bP0l+XkK(M>dg diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_focused.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_focused.png index 146e3b2c10ac826b7dada021b529e6b8894fe51b..0c5bc066211852566996cf0887123e4d1a815a3a 100644 GIT binary patch literal 101 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4>3F(0hE&W+{`3F;|9Unh25x3% yX6KX&l{nYa;dZjvs^`77Ed+!ngf|vOq`Iw jQxoWVfgz@|MS_8WMT3|7%M2+?py3Rju6{1-oD!Mqc3SGrwk+T3Ps z*X%-=BPXanY{*q)WYT_@hNC_ZVcBUs`hLLb&g>v(@b!N+)N*r?-7?!*j VE!&vSG>HKSJYD@<);T3K0RYHDEaCtF delta 105 zcmV-v0G9uf0fqsPBw|fTL_t(|oQ=>y3cxT3MA4tpI|ffUcuKp9G*E3^KW1b`L<9$S z1K6|{$;I`TBo{yr<;u1azEQ#zU!jCHE%6uHsu!*n*naG08mEIl1DhiaSnDN@00000 LNkvXXu0mjfe;O#@ diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio_clicked_and_focused.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio_clicked_and_focused.png index 22d1fb3d789d33586fbb2114e523ff4718e448f5..5525efa3266419e02c73c2c4c7e5c190e5e932ca 100644 GIT binary patch delta 118 zcmZo?oXj{uB|pm3#WAE}PIAHmvxGn2r@r04LPo#s$ZPlck~4PH8!pnYDSIT|rKHVm z#s&l;tvW9z@qj=Mx1>bf(Zv~QO4oX?grE5FUiapM{3 Uc2>Kv9}Ga?>FVdQ&MBb@07if>+5i9m delta 106 zcmV-w0G0of0fzyQBx6oVL_t(|oQ=>y3cxT3MA4tpn+818fM>O1J|4*4Zpx)r~m)} M07*qoM6N<$f^VrUTmS$7 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio_disabled.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio_disabled.png index 188ae44eb73d81e2e7a665d7b54968a1b10e1c29..7bd676414e91535d936c053559d33649e1517b97 100644 GIT binary patch delta 120 zcmV-;0Eho&mI06?a$QM8K~#90t&X7%fG`Y1-^k1eD9k*8@9fVKT7^ZBfFf)Rf!Mj; zy~`D9%ZbRTsznfEL`2+udw_X5GN|ty%bFK`8a_KCuxDWN_s9C62QV8jOYM!&h7x?6 aU%CKxT@YD0dYr%j0000nP delta 71 zcmbQnm^wklQODE8F{ENna!NwNkMj;J35f{_2?ZquM^o_8GdrhLsKmLR4%bUhP&#J9#?~gdYQn@D a4D1XXD%`wp_a7=}00K`}KbLh*2~7Zn02k;0 delta 116 zcmV-)0E_=+k^zt;Zdpl0K~#90wUA2+z#t4npT%qC8j>SQkKs;=XPT~B1f^utS>^LG z3`u|pC*0hrs7ohxH_1+gJGkfbJlJ6fd&&@990*-4e%XyXvKKuqw Wu`GvO?Wno{0000>tVrz~Uu0#8>zmvv4FO#n7m97g~E delta 106 zcmV-w0G0n}hyjo!WKT&%K~#90WBmXBKLe?NiDVrN3=AwSEiDACs;Q}AMA5{+z`!tJ z!UUWe3JMA^ZGthhw6riJr6r}Y8bp?#Mm0eJJjihcFHTL^A`l+{0O6Vpjlwk3Q~&?~ M07*qoM6N<$f}wjSN&o-= diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/small_checkbox_clicked.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/small_checkbox_clicked.png index 8fa7fbe5b461abfd95db22660b2928ce155c2e74..20b002849c57deef4c96712721c7fe4b318552e7 100644 GIT binary patch delta 110 zcmV-!0FnRO0gC~UBxh1dL_t(|oXwI!3cw%?1lQufOkS=(8xPWhBDGS4?rj4@HcJA4 zHg)X+=OH(jn>WJ?S+dSAp%DOx_i9PXC3kVg*{32ji!zJ!c+(012=M2RsBWv%iHv^Z Q_y7O^07*qoM6N<$g5psw=Kufz delta 191 zcmeBXyv;a4rGAm8i(^Q|oaBTB;t4r*_?hp(c#(w z5NJ6Qcwx$smp*f5$;H(7E&6z)^D)oLm6{T>A}+8BSV(D0tkKcoyxp?qM(xze0jix!zrTE;2Xc!Y_edxcKp5>uIv3_#%N>gTe~DWM4f-%U{9 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb.png index 59711d71c8f89862daa0d23199518525b02b750c..8f05f7c8e3c890ffd17f782529d8031e21f98670 100644 GIT binary patch literal 500 zcmV4Tx0C=2zkv&MmKpe$iTcuiB9PA*XkfAzR5Eao)s#pXIrLEAagUO{|(4-+r zad8w}3l4rPRvlcNb#-tR1i=pwM<*vm7b)?7NufoI2gm(*ckglc4)8WAOfkB~0Yx?S zR6HhRbE|^?6#<0Mi($-)%+M0)#SC1>*F8LZy^HcJ_j7-akeoLd;1P)Dn5LV=8^qI_ zrp9@nILu0-Ongo}rqcz9AGt0${KmOxvzKRv^-OA>I7}=QJ6P&qR?-#XDdLE%sFd%` zIxKVE;;d9^taVTR!eCxqNpqd%5aL)u0!avvP(v9Nn26GMN&)jR=w&%l-1_E#Ig%qQvf zwiY=8`nG|K>$WEC0hc?#z>_BGqAmGodJ6^M{fxdT2lU?pfi-jfDCoDd;=UD z0;5IBUUzwSPiJrco@w>>1GU(4!UUvzS^xk5DoI2^RCt_YWME+U|NsAghU3SNGhhRg qCQM>jv1$ba6INL)U{v8?RR{o`-U=7)a5EAB0000#0gv^-rLLn`JZrz9l&*kAv@u5ESr wdZPs9?CWb%Z`3E)GYf}B>Li3bObF#*=-$UAeuL@iY@jX%Pgg&ebxsLQ0MaHMEC2ui literal 104 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xaeNPw1kcv6UQzlRTf8K#DAu%B# zVa19SDxSga?$*r0mfH*s40^I;u6n2?sF+V=X88Y{^`hQS8BU;922WQ%mvv4FO#lR> B9GL(B diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track.png index 777213803f004417674269a4b7c6667d4fd7c2a0..46dc01750cd4f8e23d8af15dfaf5cc35e32d5498 100644 GIT binary patch literal 542 zcmV+(0^$9MP)4Tx0C=2zkv&MmKpe$iTcuiB9PA*XkfAzR5Eao)s#pXIrLEAagUO{|(4-+r zad8w}3l4rPRvlcNb#-tR1i=pwM<*vm7b)?7NufoI2gm(*ckglc4)8WAOfkB~0Yx?S zR6HhRbE|^?6#<0Mi($-)%+M0)#SC1>*F8LZy^HcJ_j7-akeoLd;1P)Dn5LV=8^qI_ zrp9@nILu0-Ongo}rqcz9AGt0${KmOxvzKRv^-OA>I7}=QJ6P&qR?-#XDdLE%sFd%` zIxKVE;;d9^taVTR!eCxqNpqd%5aL)u0!avvP(v9Nn26GMN&)jR=w&%l-1_E#Ig%qQvf zwiY=8`nG|K>$WEC0hc?#z>_BGqAmGodJ6^M{fxdT2lU?pfi-jfDCoDd;=UD z0;5IBUUzwSPiJrco@w>>1GU(4!UUvzS^xk5R7pfZRCt_YWME+U&p;tyVPIfjaBy%S z+kgWH4lpoL>I%3^uz}p%TonG~$&*PlBsVt~LlZVJ7@&%G29F_F!({U0$&?2yAx-Fj gD&ApaphB_-0KdBwk*_&N4*&oF07*qoM6N<$f&{(ky8r+H literal 322 zcmV-I0lof-P)y zF%E(-6o!8d7jR$^Jc1er77yX#rk=>n#RHk>!k~i2$%$5n4z5T{a4;xM1X?IU;$SR4NsR6QfrKz!553t+$C8Aw;N0+O)OAq#zx`fUdW`W{ln7QJPxcjDG~dLz!5T zR8|(z%v@0sKM|uB26|yqHUB-aJd}1S;}~sSx~8@hi_@%07*qoM6N<$g3Sqs;{X5v diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked.png index 8076ca0156a40a0c944a88ef45d8d005bcd43751..de41d2ea1e54ab93c713748c7055b37d7785fe42 100644 GIT binary patch literal 117 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~e!3HF=pW8M9DH~50$B>FS$tej5Kh8TaCL|^# zB&=A!Ui~(Mnpckc$%bZjej^#1iUNjMgC5=l#ju5Y_uA&h95~^S7{tafp_Sw2(HPc8 QKyw&8UHx3vIVCg!0PX}L=l}o! literal 289 zcmeAS@N?(olHy`uVBq!ia0vp^YCtT)!3HEB_WN@HsTZCujv*Cul6(3(|D88DabVI> zZ;42s(ywf{WHlaqdFssr0y-e#xZixafa3=mrmobSbCxMtBqcTQs*tz4`=l%F?QM%b zL>8=YW?X*3=?c%u^c@GLhwDpL968cibmiXJYJ(#)Ixd|&a9~1w*fAh>TT|4FS$$$R;|6k9>#lX$X z%-meR-0~)yL12=#CtFT!Nmt_Q#lg%kni6vuxHod`eR?M2F_W9uWFv-oIeg7IDfK&m P#xQug`njxgN@xNA^=~7C literal 273 zcmV+s0q*{ZP)Aasc{~S=PbW5zMNE}NOqD+rD{JZ2npdrw z)XNV5moSuGGzL;)zkERj%K?_-Rh7NHcUE+Zoq|Al`IHbqJU4WKG>4gP7@TW&Spxvx XD;hAV+4R{U00000NkvXXu0mjfiC=1n diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_and_focused.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_and_focused.png.mcmeta deleted file mode 100644 index fff81e9a3..000000000 --- a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_and_focused.png.mcmeta +++ /dev/null @@ -1,10 +0,0 @@ -{ - "gui": { - "scaling": { - "type": "nine_slice", - "width": 38, - "height": 20, - "border": 3 - } - } -} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_disabled.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_disabled.png index 4f9ecbbb5f2cb7fbf25f66a27956b1cd276a6bfc..866f35966d572bd70d10f80a70dd403564f57a21 100644 GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^fFS$tej5Kh8TSCL|^# zBp4VNDBNz~7S}T{2zy z%L;-(6o!ApeG&QsT|jz=h`?SpqF3-Cf)cvZa!#UK6KWxr138X5rqH*OGymb^s57J4 z3;T%{q65HqFl^d`V}ClvVgRsp-NFqRV=zrqsno=>0N^7A0Q1=tqmkV74u>O_tF=<8 zG@KaSJ?Y?Pv&*b8#-w_PrmbgIDq4Tx0C=2zkv&MmKpe$iTcuiB9PA*XkfAzR5Eao)s#pXIrLEAagUO{|(4-+r zad8w}3l4rPRvlcNb#-tR1i=pwM<*vm7b)?7NufoI2gm(*ckglc4)8WAOfkB~0Yx?S zR6HhRbE|^?6#<0Mi($-)%+M0)#SC1>*F8LZy^HcJ_j7-akeoLd;1P)Dn5LV=8^qI_ zrp9@nILu0-Ongo}rqcz9AGt0${KmOxvzKRv^-OA>I7}=QJ6P&qR?-#XDdLE%sFd%` zIxKVE;;d9^taVTR!eCxqNpqd%5aL)u0!avvP(v9Nn26GMN&)jR=w&%l-1_E#Ig%qQvf zwiY=8`nG|K>$WEC0hc?#z>_BGqAmGodJ6^M{fxdT2lU?pfi-jfDCoDd;=UD z0;5IBUUzwSPiJrco@w>>1GU(4!UUvzS^xk5SxH1eRCt_Y{Qv*|e+CKx3j+fK19Ji+ z*#`Vw@SlN+QdhuTf(@*#+JM3jZVMpIkhN7CFf?Hkg8`~|XYd%p`2YX^|Jb6K*ns`J l;6H9dhy_$jCydlc_5k;_B@4lO>}~)6002ovPDHLkV1l)k@|6Gp literal 325 zcmV-L0lNN)P)y zF%E(-6o!8dM{vMJh-c8mg~?kuIpPf*oxGEYP7Io$#?gtiIMBfziHSHElqLc#RwVJ; z$lv$!k*b_TO4bcF=>h*u^!QpJ(hZq2?97nnVAq1vrs&vB8vH;*E1_1VWg#JKm zdhW)>bT(Him4*|eCkDV3DqJkrnHeEOs7Kzkwany0x`qK=Z@tYJJHw+ix4xPFh@z`9 zu`H>gETLJr;zv{>M*RTP52R}Tdtzny+Nn%qblTE2wVfCz7RTcE`3?DN89SHC627*T z9;Jmp(L6Y@FuY)QI0_Ypmnbt+aj|n^oBj6VhN^gqCC?2h%G3y=0gs1S-Dt?1#45Q1 Xl$lB_Y7c0&00000NkvXXu0mjfqR@=u diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_focused.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_focused.png.mcmeta deleted file mode 100644 index fff81e9a3..000000000 --- a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_focused.png.mcmeta +++ /dev/null @@ -1,10 +0,0 @@ -{ - "gui": { - "scaling": { - "type": "nine_slice", - "width": 38, - "height": 20, - "border": 3 - } - } -} From 84783ecb81fb0ed258f8818134581791919fca67 Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Wed, 12 Aug 2026 19:30:32 -0400 Subject: [PATCH 18/30] Fix MutableInteractionSource silently dropping the newest interaction under load flow = MutableSharedFlow(extraBufferCapacity = 16) used the default SUSPEND overflow strategy - fine for a suspending emit(), but tryEmit() can't suspend, so once the buffer fills, tryEmit() simply fails and the interaction is dropped. For a state-defining interaction like FocusInteraction.Focus/Unfocus (collectIsFocusedAsState only knows what it actually received), dropping the *newest* one under contention (e.g. rapid Tab presses outrunning the Recomposer's own dispatch) means the visual focus state silently stops updating - matches a reported "tabbing through inputs sometimes doesn't trigger the texture state" symptom exactly. Switched to BufferOverflow.DROP_OLDEST: tryEmit() now never fails, and only a stale already-superseded buffered event is ever discarded - harmless, since collectAsState's reducer only cares about the latest value of each interaction type anyway. Full live GameTest suite: 23/23 passing. --- .../archie/gui/interaction/InteractionSource.kt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/interaction/InteractionSource.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/interaction/InteractionSource.kt index 1414d0440..8c89d70ad 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/interaction/InteractionSource.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/interaction/InteractionSource.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow @@ -20,7 +21,7 @@ interface InteractionSource { /** An [InteractionSource] that can also emit new [Interaction]s into itself. */ interface MutableInteractionSource : InteractionSource { - /** Emits [interaction] without suspending, dropping it if the internal buffer is full. */ + /** Emits [interaction] without suspending. Never fails to enqueue - see [MutableInteractionSourceImpl]. */ fun tryEmit(interaction: Interaction): Boolean } @@ -28,7 +29,16 @@ interface MutableInteractionSource : InteractionSource { fun MutableInteractionSource(): MutableInteractionSource = MutableInteractionSourceImpl() private class MutableInteractionSourceImpl : MutableInteractionSource { - private val flow = MutableSharedFlow(extraBufferCapacity = 16) + // DROP_OLDEST (not the default SUSPEND, which makes a non-suspending tryEmit() on a full + // buffer fail and silently drop the *newest* interaction instead) - a state-defining + // interaction like FocusInteraction.Focus/Unfocus has to never go missing, since + // collectIsFocusedAsState() only reduces off whatever interactions it actually receives. + // A missed Unfocus is exactly "sometimes Tab-focusing doesn't visually update" (a stale + // collector - e.g. rapid Tab presses outrunning the Recomposer's own dispatch - could + // previously fill the 16-slot buffer and start rejecting new emissions outright). Losing a + // stale buffered event this way is harmless since only the latest of each interaction type + // ever matters to the reducer; losing the newest one wasn't. + private val flow = MutableSharedFlow(extraBufferCapacity = 16, onBufferOverflow = BufferOverflow.DROP_OLDEST) override val interactions: Flow = flow override fun tryEmit(interaction: Interaction): Boolean = flow.tryEmit(interaction) } From d84c73b1167e2526e2da85d0fc4e1b888ec70c86 Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Wed, 12 Aug 2026 19:36:09 -0400 Subject: [PATCH 19/30] Use genuine OreUI dark-mode recolors for the bedrock/dark theme, fix surface asset surface: background_panel turned out to be unreferenced anywhere in ui_common.json/ui_template_buttons.json - dialog_background_opaque is ui_common.json's own actual default $dialog_background. Switched to that (the same mistake pattern as the earlier button/checkbox mixups: picking an asset by its name alone instead of verifying it against a real template reference). dark theme variant: previously just copied the light variant's own JSON/textures verbatim (matching how the original CurseForge reference pack's own light/dark split turned out to mostly just be a surface recolor). Rebuilt using the user's locally-owned "OreUIDarkM" dark-mode pack instead - confirmed via minecraft.wiki's own Ore UI documentation (which points back at the same resource_pack/textures/ui/ directory) and the pack's own file layout (exclusively targets that same directory) that this is genuinely Ore UI's own asset set, not a different/legacy one. Every component that has a dark-pack recolor (button, checkbox, radio, switch, slider, slider_handle, tab_menu, surface) now uses it for the dark theme variant, falling back to the light art only for the few files the pack doesn't include (checkbox_filled's radio-checked texture, text_edit_base/hover). Full live GameTest suite: 23/23 passing, zero sprite metadata parse errors. --- .../archie_themes/bedrock/dark/checkbox.json | 10 +++++----- .../archie_themes/bedrock/dark/energy_bar.json | 2 +- .../archie_themes/bedrock/dark/fluid_tank.json | 2 +- .../bedrock/dark/progress_bar.json | 2 +- .../archie_themes/bedrock/dark/radio.json | 10 +++++----- .../archie_themes/bedrock/dark/slider.json | 6 +++--- .../bedrock/dark/slider_handle.json | 6 +++--- .../bedrock/dark/small_checkbox.json | 4 ++-- .../bedrock/dark/switch_thumb.json | 4 ++-- .../bedrock/dark/switch_track.json | 10 +++++----- .../archie_themes/bedrock/dark/tab_menu.json | 10 +++++----- .../sprites/bedrock/button_clicked_dark.png | Bin 100 -> 109 bytes .../gui/sprites/bedrock/button_dark.png | Bin 103 -> 112 bytes .../sprites/bedrock/button_disabled_dark.png | Bin 112 -> 2733 bytes .../bedrock/button_highlighted_dark.png | Bin 104 -> 118 bytes .../checkbox_clicked_and_focused_dark.png | Bin 0 -> 179 bytes .../sprites/bedrock/checkbox_clicked_dark.png | Bin 0 -> 198 bytes .../gui/sprites/bedrock/checkbox_dark.png | Bin 0 -> 128 bytes .../sprites/bedrock/checkbox_disabled_dark.png | Bin 0 -> 125 bytes .../sprites/bedrock/checkbox_focused_dark.png | Bin 0 -> 127 bytes .../gui/sprites/bedrock/energy_bar_dark.png | Bin 0 -> 78 bytes .../sprites/bedrock/energy_bar_dark.png.mcmeta | 10 ++++++++++ .../gui/sprites/bedrock/fluid_tank_dark.png | Bin 0 -> 78 bytes .../sprites/bedrock/fluid_tank_dark.png.mcmeta | 10 ++++++++++ .../gui/sprites/bedrock/progress_bar_dark.png | Bin 0 -> 78 bytes .../bedrock/progress_bar_dark.png.mcmeta | 10 ++++++++++ .../bedrock/radio_clicked_and_focused_dark.png | Bin 0 -> 147 bytes .../gui/sprites/bedrock/radio_clicked_dark.png | Bin 0 -> 146 bytes .../gui/sprites/bedrock/radio_dark.png | Bin 0 -> 128 bytes .../sprites/bedrock/radio_disabled_dark.png | Bin 0 -> 150 bytes .../gui/sprites/bedrock/radio_focused_dark.png | Bin 0 -> 127 bytes .../gui/sprites/bedrock/slider_dark.png | Bin 0 -> 78 bytes .../gui/sprites/bedrock/slider_dark.png.mcmeta | 10 ++++++++++ .../gui/sprites/bedrock/slider_handle_dark.png | Bin 0 -> 130 bytes .../bedrock/slider_handle_dark.png.mcmeta | 10 ++++++++++ .../bedrock/slider_handle_highlighted_dark.png | Bin 0 -> 142 bytes .../slider_handle_highlighted_dark.png.mcmeta | 10 ++++++++++ .../bedrock/slider_highlighted_dark.png | Bin 0 -> 78 bytes .../bedrock/slider_highlighted_dark.png.mcmeta | 10 ++++++++++ .../bedrock/small_checkbox_clicked_dark.png | Bin 0 -> 201 bytes .../sprites/bedrock/small_checkbox_dark.png | Bin 0 -> 133 bytes .../textures/gui/sprites/bedrock/surface.png | Bin 159 -> 161 bytes .../gui/sprites/bedrock/surface_dark.png | Bin 162 -> 135 bytes .../gui/sprites/bedrock/switch_thumb_dark.png | Bin 0 -> 507 bytes .../bedrock/switch_thumb_disabled_dark.png | Bin 0 -> 106 bytes .../switch_track_clicked_and_focused_dark.png | Bin 0 -> 144 bytes .../bedrock/switch_track_clicked_dark.png | Bin 0 -> 144 bytes .../gui/sprites/bedrock/switch_track_dark.png | Bin 0 -> 557 bytes .../bedrock/switch_track_disabled_dark.png | Bin 0 -> 156 bytes .../bedrock/switch_track_focused_dark.png | Bin 0 -> 557 bytes .../gui/sprites/bedrock/tab_menu_dark.png | Bin 0 -> 136 bytes .../sprites/bedrock/tab_menu_dark.png.mcmeta | 10 ++++++++++ .../sprites/bedrock/tab_menu_disabled_dark.png | Bin 0 -> 136 bytes .../bedrock/tab_menu_disabled_dark.png.mcmeta | 10 ++++++++++ .../sprites/bedrock/tab_menu_focused_dark.png | Bin 0 -> 137 bytes .../bedrock/tab_menu_focused_dark.png.mcmeta | 10 ++++++++++ .../sprites/bedrock/tab_menu_selected_dark.png | Bin 0 -> 114 bytes .../bedrock/tab_menu_selected_dark.png.mcmeta | 10 ++++++++++ .../tab_menu_selected_highlighted_dark.png | Bin 0 -> 114 bytes ...b_menu_selected_highlighted_dark.png.mcmeta | 10 ++++++++++ 60 files changed, 153 insertions(+), 33 deletions(-) create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_clicked_and_focused_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_clicked_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_disabled_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_focused_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/energy_bar_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/energy_bar_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/fluid_tank_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/fluid_tank_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/progress_bar_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/progress_bar_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio_clicked_and_focused_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio_clicked_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio_disabled_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio_focused_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_handle_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_handle_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_handle_highlighted_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_handle_highlighted_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_highlighted_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_highlighted_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/small_checkbox_clicked_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/small_checkbox_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb_disabled_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_and_focused_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_disabled_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_focused_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_disabled_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_disabled_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_focused_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_focused_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_selected_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_selected_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_selected_highlighted_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_selected_highlighted_dark.png.mcmeta diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/checkbox.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/checkbox.json index 1dd19cea5..e7af13d78 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/checkbox.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/checkbox.json @@ -1,7 +1,7 @@ { "states": { "default": { - "texture": "archie:bedrock/checkbox", + "texture": "archie:bedrock/checkbox_dark", "texture_size": { "width": 10, "height": 10 @@ -10,16 +10,16 @@ "height": 10 }, "focused": { - "texture": "archie:bedrock/checkbox_focused" + "texture": "archie:bedrock/checkbox_focused_dark" }, "clicked": { - "texture": "archie:bedrock/checkbox_clicked" + "texture": "archie:bedrock/checkbox_clicked_dark" }, "clicked_and_focused": { - "texture": "archie:bedrock/checkbox_clicked_and_focused" + "texture": "archie:bedrock/checkbox_clicked_and_focused_dark" }, "disabled": { - "texture": "archie:bedrock/checkbox_disabled" + "texture": "archie:bedrock/checkbox_disabled_dark" } } } diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/energy_bar.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/energy_bar.json index c2193bd3d..6e49955ce 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/energy_bar.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/energy_bar.json @@ -1,7 +1,7 @@ { "states": { "default": { - "texture": "archie:bedrock/energy_bar", + "texture": "archie:bedrock/energy_bar_dark", "texture_size": { "width": 3, "height": 3 diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/fluid_tank.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/fluid_tank.json index 7aa18dd4a..b75421af0 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/fluid_tank.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/fluid_tank.json @@ -1,7 +1,7 @@ { "states": { "default": { - "texture": "archie:bedrock/fluid_tank", + "texture": "archie:bedrock/fluid_tank_dark", "texture_size": { "width": 3, "height": 3 diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/progress_bar.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/progress_bar.json index 12f09b408..9299ac686 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/progress_bar.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/progress_bar.json @@ -1,7 +1,7 @@ { "states": { "default": { - "texture": "archie:bedrock/progress_bar", + "texture": "archie:bedrock/progress_bar_dark", "texture_size": { "width": 3, "height": 3 diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/radio.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/radio.json index e4cd76815..ad063caae 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/radio.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/radio.json @@ -1,7 +1,7 @@ { "states": { "default": { - "texture": "archie:bedrock/radio", + "texture": "archie:bedrock/radio_dark", "texture_size": { "width": 10, "height": 10 @@ -10,16 +10,16 @@ "height": 10 }, "focused": { - "texture": "archie:bedrock/radio_focused" + "texture": "archie:bedrock/radio_focused_dark" }, "clicked": { - "texture": "archie:bedrock/radio_clicked" + "texture": "archie:bedrock/radio_clicked_dark" }, "clicked_and_focused": { - "texture": "archie:bedrock/radio_clicked_and_focused" + "texture": "archie:bedrock/radio_clicked_and_focused_dark" }, "disabled": { - "texture": "archie:bedrock/radio_disabled" + "texture": "archie:bedrock/radio_disabled_dark" } } } diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider.json index b1c96aff6..09ed027c4 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider.json @@ -1,7 +1,7 @@ { "states": { "default": { - "texture": "archie:bedrock/slider", + "texture": "archie:bedrock/slider_dark", "texture_size": { "width": 3, "height": 3 @@ -10,10 +10,10 @@ "height": 20 }, "focused": { - "texture": "archie:bedrock/slider_highlighted" + "texture": "archie:bedrock/slider_highlighted_dark" }, "clicked": { - "texture": "archie:bedrock/slider_highlighted" + "texture": "archie:bedrock/slider_highlighted_dark" } } } diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_handle.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_handle.json index 1d04b1ed1..c36dcccd6 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_handle.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_handle.json @@ -1,7 +1,7 @@ { "states": { "default": { - "texture": "archie:bedrock/slider_handle", + "texture": "archie:bedrock/slider_handle_dark", "texture_size": { "width": 6, "height": 6 @@ -10,10 +10,10 @@ "height": 20 }, "focused": { - "texture": "archie:bedrock/slider_handle_highlighted" + "texture": "archie:bedrock/slider_handle_highlighted_dark" }, "clicked": { - "texture": "archie:bedrock/slider_handle_highlighted" + "texture": "archie:bedrock/slider_handle_highlighted_dark" } } } diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/small_checkbox.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/small_checkbox.json index 2fa339c3e..f71ba064d 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/small_checkbox.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/small_checkbox.json @@ -1,7 +1,7 @@ { "states": { "default": { - "texture": "archie:bedrock/small_checkbox", + "texture": "archie:bedrock/small_checkbox_dark", "texture_size": { "width": 13, "height": 13 @@ -10,7 +10,7 @@ "height": 13 }, "clicked": { - "texture": "archie:bedrock/small_checkbox_clicked" + "texture": "archie:bedrock/small_checkbox_clicked_dark" } } } diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_thumb.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_thumb.json index 3e70e0c46..287315c24 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_thumb.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_thumb.json @@ -1,7 +1,7 @@ { "states": { "default": { - "texture": "archie:bedrock/switch_thumb", + "texture": "archie:bedrock/switch_thumb_dark", "texture_size": { "width": 10, "height": 12 @@ -10,7 +10,7 @@ "height": 12 }, "disabled": { - "texture": "archie:bedrock/switch_thumb_disabled" + "texture": "archie:bedrock/switch_thumb_disabled_dark" } } } diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json index 9799705f5..e81a82d57 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json @@ -1,7 +1,7 @@ { "states": { "default": { - "texture": "archie:bedrock/switch_track", + "texture": "archie:bedrock/switch_track_dark", "texture_size": { "width": 17, "height": 12 @@ -10,10 +10,10 @@ "height": 12 }, "focused": { - "texture": "archie:bedrock/switch_track_focused" + "texture": "archie:bedrock/switch_track_focused_dark" }, "clicked": { - "texture": "archie:bedrock/switch_track_clicked", + "texture": "archie:bedrock/switch_track_clicked_dark", "texture_size": { "width": 16, "height": 12 @@ -22,7 +22,7 @@ "height": 12 }, "clicked_and_focused": { - "texture": "archie:bedrock/switch_track_clicked_and_focused", + "texture": "archie:bedrock/switch_track_clicked_and_focused_dark", "texture_size": { "width": 16, "height": 12 @@ -31,7 +31,7 @@ "height": 12 }, "disabled": { - "texture": "archie:bedrock/switch_track_disabled" + "texture": "archie:bedrock/switch_track_disabled_dark" } } } diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/tab_menu.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/tab_menu.json index 6b9ae8171..e6e9c2078 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/tab_menu.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/tab_menu.json @@ -1,7 +1,7 @@ { "states": { "default": { - "texture": "archie:bedrock/tab_menu", + "texture": "archie:bedrock/tab_menu_dark", "texture_size": { "width": 12, "height": 11 @@ -10,16 +10,16 @@ "height": 24 }, "focused": { - "texture": "archie:bedrock/tab_menu_focused" + "texture": "archie:bedrock/tab_menu_focused_dark" }, "clicked": { - "texture": "archie:bedrock/tab_menu_selected" + "texture": "archie:bedrock/tab_menu_selected_dark" }, "clicked_and_focused": { - "texture": "archie:bedrock/tab_menu_selected_highlighted" + "texture": "archie:bedrock/tab_menu_selected_highlighted_dark" }, "disabled": { - "texture": "archie:bedrock/tab_menu_disabled" + "texture": "archie:bedrock/tab_menu_disabled_dark" } } } diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_clicked_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_clicked_dark.png index 46189b22a6aedf1017e6a3ee9db080dd77a6c620..c832a518be715575904af4bba4e48b869d8deb43 100644 GIT binary patch literal 109 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>1|%O$WD@{VCY~;iAr*6yOJa4u&u8Qp5PX`J zkeHB=@Z$^1_C@pO?=v!ZXw1siwnj-zFfgwTDDWthg&|@Om*UB?f-s1|%O$WD@{V=AJH&Ar*6yzkK=f=ez^M1_L0d z`SF2qITIrzBb%DjG9dp#v>-P#^O3?I42v5Y7!E2ZoU&rDI>qL9QkJU^Xas|&tDnm{ Hr-UW|9^W5J literal 103 zcmeAS@N?(olHy`uVBq!ia0vp^EFjFm1|(O0oL2{=^gLZ0Ln`LPp4iCCpuod?kUe$Z zoBd6??**h>4o=;^t9pk@AcH5vhO^ItCvEuqh{39pe>H3Ss(V1444$rjF6*2UngDZLqkwWLqi}?a&Km7Y-Iodc$}S9)Gc>Uwq5=^`LdiiLn+!5)wxpu}JlQBip_ zvQ~8E<-M1e-ydgvYoERMJ!kKI*17~vAmcQ7uoQ$&mudEnVrUCi&%W-40ak@%snFBnkD3j z81WZzQ5Khze=Xu$BGyb5rg265RveqgVP*n=B8lw+4l7B-rXnWs!$RCdyc7T&De)_g z|B3~i9D(>!Zs{4hd~RZrfUe8Zqnp{{O0GU=+k;r7-zyx?6f$29uWyz~Y@h zOGaEq$vHg`_dOZM)Sy63ve6hvv z1)yUy0P^?0*fb9UASvow`@mQCp^4`uNg&9uGcn1|&Nk+9SjOUl{-OWr@Hh0;_l(8q z{wNRKos+;6rV8ldy0Owz(}jF`W(JeRp&R{qi2rfmU!TJ;gpa^+=koqj6aQh@pR_pFB2gMX0cxxCN~9iXLAsD$ClpY8|Q+RgF4=YDe{;ZlXp}GpJ8!GFl03fVM;Xpd-@3{{8fLrtcZ zP`{)0Q)gslWG!XGWpiX}WY5Ts&=8t7&4-psE2EvD-J!jgQfv(`8kfN|tp+n)3B1%zTF<3EM@qpqb#pxx~CH6~Le@lv&oLF*S z30Bfq3Q=04bV#XBX;xW9*-JS?d9U(CNC15 z-G!b?ucG(RXVjF`yw!wib!z=;^XfY4%he0iTh$+F5HuJX2^tj|-5N8Rs+s|s`I^m| z_qFg^46P)sJzBk5bJ{xEe-YYSv^%sXb>ww?bn(=Yu(!=O6^iuTp z>)p_Y^{w=i^lS773}6Fm1Fpe-gF!>Ip{*g$u-szvGhed;vo5pW&GpS$<~8QGEXWp~e-`U4IxSvW8d!2H z4_Mx{qF4o3ZL#XM`efGt8hef*7TYE4FA`SKIZrr)}TaS=$Nh zPT2isZ)Bfhf7E_*sm@Z)(uSpD4(bj}hdPH5N4jI2<3Yy}Cp9OgQ@zs@XANhzbEETw zi=Ioe%Q2T1uBNVhf7dqGIX64EwQlF#5qB^5V)uRz8IR>2)gF&M)jbnEn>}Z|ti0BE zo%cq2`+4v59`;f8Vfi%q%=p^)uJ!HlBl(5;Rr@{h*Z1f9cLl%!z5%-e9xl^b##`1A z2m*ZqcLhEQ(g|7}^kXn4I4HO#_-Tk)NPb9fC?zyD^l0dte=yguvakosb(ag5cZW-c z$AmY9&qcULlt+w2nnbRRydI?(#f|EW#zu!nH%8B{@K~{X#dwTWOi|38l{zbPR$g7D zxGHtknOMnKX6(s0bX-K-(YO!HKxRF2Hr^+GU;GTqjkSmMobAl6U{7%zIOUv)1c!w3 zgvmsQ#9fI~e@RYBl}XcFH*PieWwLj2ZSq`7V9Mc?h17`D)-+sNT-qs~3@?S(ldh7U zlRlVXkWrK|vf6I-?$tAVKYn8-l({mqQ$Q8{O!WzMg`0(=S&msXS#Pt$vrpzo=kRj+ za`kh!z=6$;cwT88(J6|n-WB%w`m$h~4pmp)GHhtv0VYX+AHW4#TBo2$L_vb zX<1pje}}L~xM!rwzp8Vu=H4B9KU61G->z}3Y2Bx^Z`;1P{p|fi2b>SI)GF7O)V@E+ zJ$SdytFFCXyT0-e=1|t5rw!o^z27pvZE93(ENT3Bn0I*ONXU_%CYz?Fqe@51n&D<) z^VG4JV>iBY|E{yesHLuz)>?8L92Xvc_I=#6$aLXUfhJ&K90sIG1;B_I$?q=?jS$#=2v> zA6$&Qc&jJ4r~i`Qr7M>`FJJ6+={@`mOuh ze+O&^I&awC=o(x)cy`EX=)z6+o0o6-+`4{y+3mqQ%kSJBju{@g%f35#FZJHb`&swr zA8dGtepviS>QUumrN{L@>;2q1Vm)$Z)P1z?N$8UYW2~{~zhwUMVZ87u`Dx{Z>O|9| z`Q+&->FRy-Sjp7DHsy69KwU-!Mxe|_=8Z_dB<%|y>t*#K5*r-oMDz#9(3M6eOMG)CcBuz%7o@I6~tHjUzOUAJ8~L f;|Pr-G>*_L{WJlJ%Shv#00000NkvXXu0mjf1O!)6 delta 83 zcmZ20S};M`m%-fA#WAE}&fD{iybJ~$Ee@s6=kM3MxMaygwFbYUSInQS9*DVT$Sf>u dIVLED#1JctVQSmQA<4`D1fH&bF6*2UngDJS9?$>) diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted_dark.png index 181a28dea070ee5c5bfee204535747c86ae2e7a3..3aa88bbc3570b18608c0e40b900267b5fe9d3c9e 100644 GIT binary patch literal 118 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>1|%O$WD@{Vww^AIAr*6yOJa4u&u8Qp5PX`J zkeHB=@Z$^1_C@pO?=v!ZXw1s?Voisu%aqv@pGC6WLD literal 104 zcmeAS@N?(olHy`uVBq!ia0vp^EFjFm1|(O0oL2{=^gUf1Ln`JZ^F#^!n$O5}E%x4l z1BVVAIItn_ZB++{IWw~`vs6NgM%v%=A4Jx5Gcrsx;{5Qb>%eZHRt8U3KbLh*2~7aW C-XWp@ diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_clicked_and_focused_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_clicked_and_focused_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..ed714d95611702078876e3a3d667c2b70b2ff6de GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4Re8ENhE&W+PFP@;FlFMzzy3W= zVr)Qgw$5!1my63zNxtJXjT$Kl2?;;GumoT1Y-;Dp%YGs$0R%G&W|k~^pvX0!bGAy) zG(QpNQe_?w^)5r3x&OJ&O}qNsB~(M{)Y$_EK1_9tZkv2?gL6|O5IEkREqs&bQ8PQk Y5ibd!(;vkS0$su2>FVdQ&MBb@0ILo_DF6Tf literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_clicked_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_clicked_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..74e6d6e893b57ebfaf3226b393a340e87ae4622a GIT binary patch literal 198 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f0dAc};RLn`9GI8SH^A0Qti3te_ z7v7{FS?S<1W%k2@GvXaiY;0_8{{9A{b2TbXJlN(ouMq?|r+cb%$u0UYbA#uhjF&Ek zA?Ak~N)|~RzWj6jgPh1(zmQg?MR$03c;Y3uq*_SwSgS~Yz?Qw16VE0jfGqoe{wcfO oa;H1n+nKkgUtr|+VOL~ec%LmJaVTHK0q8IWPgg&ebxsLQ0Esn7vj6}9 literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..7038dd02998ff18872d41323eecde8af0366357c GIT binary patch literal 128 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4xqG@ehE&YCI$clFbq!@zs7k9B6n?|<{BHLH b-hB*D>^RgTe~DWM4f_ERaq literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_disabled_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/checkbox_disabled_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..58af9465d29e7f4bd6a9eaa84941d0591d8c8d86 GIT binary patch literal 125 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4xp=xbhE&YCI$f4xp}%chE&YCI$Uj(IoZkFZ!Dto*K2 b(X9V-8pqS;8$9)ZCNg-s`njxgN@xNA=j$qQ literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/energy_bar_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/energy_bar_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..7bb3e212768d44eb98aad1fce2997f487357a1ab GIT binary patch literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~qMj~}Ar*6yr%asq_dElu503{! bD>H-sLB^;B)dw_z3K%?H{an^LB{Ts5UzHMq literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/energy_bar_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/energy_bar_dark.png.mcmeta new file mode 100644 index 000000000..5630bc1ac --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/energy_bar_dark.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 3, + "height": 3, + "border": 1 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/fluid_tank_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/fluid_tank_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..7bb3e212768d44eb98aad1fce2997f487357a1ab GIT binary patch literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~qMj~}Ar*6yr%asq_dElu503{! bD>H-sLB^;B)dw_z3K%?H{an^LB{Ts5UzHMq literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/fluid_tank_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/fluid_tank_dark.png.mcmeta new file mode 100644 index 000000000..5630bc1ac --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/fluid_tank_dark.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 3, + "height": 3, + "border": 1 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/progress_bar_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/progress_bar_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..7bb3e212768d44eb98aad1fce2997f487357a1ab GIT binary patch literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~qMj~}Ar*6yr%asq_dElu503{! bD>H-sLB^;B)dw_z3K%?H{an^LB{Ts5UzHMq literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/progress_bar_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/progress_bar_dark.png.mcmeta new file mode 100644 index 000000000..5630bc1ac --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/progress_bar_dark.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 3, + "height": 3, + "border": 1 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio_clicked_and_focused_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio_clicked_and_focused_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..5525efa3266419e02c73c2c4c7e5c190e5e932ca GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4MR~e7hE&W+PFP@;@aOx~xBFMf z=(int?LJ>}#*TW!MH)6`kHou_w7JdLfIy^G=fxx*5Xj+{l&CwpI3rEzTJM$c6F=T- r-~PYihyqV%>gVf;zoeFzdT=prJR{xCY8UncXbXdf4MS8k8hE&W+PFP@-k(c*RUq`2> zarW%l|CLuKCnh8$+}QC-w<}GX+pO)HT?ljJ1l5NPxr)rphh;@teU4a7{KXY10R+p= s^d!tM5{|4;Zr--eY?)JuV-5qulJ}xz8}pea0WD$hboFyt=akR{0C7Yw@Bjb+ literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..7038dd02998ff18872d41323eecde8af0366357c GIT binary patch literal 128 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4xqG@ehE&YCI$clFbq!@zs7k9B6n?|<{BHLH b-hB*D>^RgTe~DWM4f_ERaq literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio_disabled_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/radio_disabled_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..7bd676414e91535d936c053559d33649e1517b97 GIT binary patch literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4#d^9phE&X1+qaOf!GOo*{)saj z8fR=8-@iF86xHfFp+PB!r}4(ldwWldW}WUlv2vBGK(dR=o!aV#N-Luk?`2NU60eoA yvsT`ap78nm@m~wr4Wtdc-}YW;6RMdVzl)(PRv^-{YR&f4xp}%chE&YCI$Uj(IoZkFZ!Dto*K2 b(X9V-8pqS;8$9)ZCNg-s`njxgN@xNA=j$qQ literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..7bb3e212768d44eb98aad1fce2997f487357a1ab GIT binary patch literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~qMj~}Ar*6yr%asq_dElu503{! bD>H-sLB^;B)dw_z3K%?H{an^LB{Ts5UzHMq literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_dark.png.mcmeta new file mode 100644 index 000000000..5630bc1ac --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_dark.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 3, + "height": 3, + "border": 1 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_handle_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_handle_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..0eedb9db1a9f5fdc542ca664a1cd51c5079c0bca GIT binary patch literal 130 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>1|%O$WD@{Vo}Mm_Ar*6yr%asq_q+qc1_K~q zVPks`vZ3xTTZa-G8(W)s{=EbZ0YSmU1Q1Y45xv;;;M&2SV=G@=Sm+$G>D}Agb{&yz=>Qta;OXk;vd$@?2>|-4DpLRe literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_handle_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_handle_dark.png.mcmeta new file mode 100644 index 000000000..f89033b90 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_handle_dark.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 6, + "height": 6, + "border": 2 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_handle_highlighted_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_handle_highlighted_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..b45a54357a46fb85afcadd4fd4d552764815ddc2 GIT binary patch literal 142 zcmeAS@N?(olHy`uVBq!ia0vp^Y#_`5A|IT2?*XJjJzX3_D&{0lnK<$9`3BYv20&0T ze@aTl=SQ~8K+xRLqHs9u^K*etbNze9e=3+R{%B)d)WA90uwb#7&hE1c|H7s;0>Q(j kAF|mz*&^B8Vx}=Od{7mg*^>481<(QpPgg&ebxsLQ0Eg8wqW}N^ literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_handle_highlighted_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_handle_highlighted_dark.png.mcmeta new file mode 100644 index 000000000..f89033b90 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_handle_highlighted_dark.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 6, + "height": 6, + "border": 2 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_highlighted_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_highlighted_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..7bb3e212768d44eb98aad1fce2997f487357a1ab GIT binary patch literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~qMj~}Ar*6yr%asq_dElu503{! bD>H-sLB^;B)dw_z3K%?H{an^LB{Ts5UzHMq literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_highlighted_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_highlighted_dark.png.mcmeta new file mode 100644 index 000000000..5630bc1ac --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_highlighted_dark.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 3, + "height": 3, + "border": 1 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/small_checkbox_clicked_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/small_checkbox_clicked_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..6c8e3e6dff5bd0a12085b4a07f4c8f32f8fad67b GIT binary patch literal 201 zcmeAS@N?(olHy`uVBq!ia0vp@Ak4uAB#T}@sR2?GJY5_^D&{0lnK<$9c?YG0#Ds)| z3vbd@Rs^_AncWBk4-3wSv#AB;Hr{4oo#pc^ml@{|C_^NZsC3PfbG-+A| zeh@e}%}?a;Tbmh8%Rk32{E+(9^5bn69-w&+Eb7dM!`X5r{n!rkaqeKs&G+N+?J+Xi w^#A|PKMJ?Rmf6ZYvFxw0H+=h0!Bd#Qt4_}RE{oPVpxYQcUHx3vIVCg!0J8x~v;Y7A literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/small_checkbox_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/small_checkbox_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..b0041378d801965f8ce8f814b1010d70d2370a0c GIT binary patch literal 133 zcmeAS@N?(olHy`uVBq!ia0vp@Ak4uAB#T}@sR2?xo-U3d6?2lOOq}@lyn|9gVnRZ~ zhRxg6R|GgXG&BN1;^kw#JU&ZqF>U8$TNl5-FL+@`2ge-WX}*hREof+MWE3nsanP%D ew!}%#YKEhCIlioC{CoyzE`z75pUXO@geCy{=rC>o literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface.png index 4f6757e4fa850b98068b4a4dff4be5f7fbc6fe13..579ebc4b4f200ffa07bc6e9bfc016c12e11157fb 100644 GIT binary patch delta 132 zcmV-~0DJ$R0igkqBzboIYh4E9oB=Si(I5=ZI@Xpy;DZ7Eh`LTdl02pK ms)5XG3v7OA+Q&bted7hlb0Kq_31dJ20000JaDlB^W3a zN<&go2Z#Yodn2j2%9+;T``KzQ-%A@Qwr`kg6^6d?J@e_QK;20tR4_kmj2-UDz kL5%Tr3iFZya16fl3aWT;E*z$Q%>V!Z07*qoM6N<$g0*Zoo&W#< diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface_dark.png index 75833a21c1e5677de32ad616617ea0a37a96e8c9..cee6179ee376893c92f5c91979a31d6e2590bd33 100644 GIT binary patch delta 106 zcmV-w0G0ou0fzyQBx6oVL_t(|obA)G4Zt7_MA1jY0IbEttmO?z5k*N0Zn^&dlcjUP zlt_i%`xZRLcmxm;OK@uqTwB$@9KjLbTjIN{)fCKKI3n^V6v=nK0f({-QBw#8=Kufz M07*qoM6N<$g2xgp?EnA( delta 133 zcmV;00DAw20ipqrBz|d0L_t(|ob8jb4Zt7}0}qr1ejZj}?O)?t${!(V5=4>$5M2~k zmi}$IumiBOfbAkV=V6h%qpASJSuuc!kW#uQXx)1-v&!SF_=(tm`3*j3(3S|!Cb~N! n@;VMDP}S)cmQnx+EYA4=MhYS%aKR!000000NkvXXu0mjftspkG diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..0a3dbace574ec03147873a5850977706b0dc6f87 GIT binary patch literal 507 zcmV4Tx0C=2zkv&MmKpe$iTcuiB9PA*XkfAzR5Eao)s#pXIrLEAagUO{|(4-+r zad8w}3l4rPRvlcNb#-tR1i=pwM<*vm7b)?7NufoI2gm(*ckglc4)8WAOfkB~0Yx?S zR6HhRbE|^?6#<0Mi($-)%+M0)#SC1>*F8LZy^HcJ_j7-akeoLd;1P)Dn5LV=8^qI_ zrp9@nILu0-Ongo}rqcz9AGt0${KmOxvzKRv^-OA>I7}=QJ6P&qR?-#XDdLE%sFd%` zIxKVE;;d9^taVTR!eCxqNpqd%5aL)u0!avvP(v9Nn26GMN&)jR=w&%l-1_E#Ig%qQvf zwiY=8`nG|K>$WEC0hc?#z>_BGqAmGodJ6^M{fxdT2lU?pfi-jfDCoDd;=UD z0;5IBUUzwSPiJrco@w>>1GU(4!UUvzS^xk5F-b&0RCt_Yl#!AB9~c#0g3_V>OLn`JZi-?N;%}-7~{Ndwc zcea`a&h>9rI3FS$x|jy{CnPkfrX9D z;Lgrs_SG`oY4*x-XRmXAoPGY&OHRe#ADPuoZBdpiSoGi_!|sF^QU{olBYY+tUmf7; u%DSKN$AN{;?FG9V{Mq(DTh7A6&R}p&VDhwWn`Z*8VDNPHb6Mw<&;$U#yEB3S literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..e9b4608676c978697f981b35b38c7a20f1b1c2ca GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~e!3HF=pW8M9sc=sh$B>FS$x|jy{CnPkfrX9D zU{B>|w$(D-Y4*x-XRmXAoPGY&OHRe#ADPuoZBdpiSoGi_!|sF^QU{olBYY+tUmf7$ u!m^+7$AN{;?FG9V{Mq(DTh7A6&M>b{U~&uRqGdoU7(8A5T-G@yGywp>YBKo% literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..df3551868a0c9dc75721a5af91d9a0e06392ec07 GIT binary patch literal 557 zcmV+|0@D47P)4Tx0C=2zkv&MmKpe$iTcuiB9PA*XkfAzR5Eao)s#pXIrLEAagUO{|(4-+r zad8w}3l4rPRvlcNb#-tR1i=pwM<*vm7b)?7NufoI2gm(*ckglc4)8WAOfkB~0Yx?S zR6HhRbE|^?6#<0Mi($-)%+M0)#SC1>*F8LZy^HcJ_j7-akeoLd;1P)Dn5LV=8^qI_ zrp9@nILu0-Ongo}rqcz9AGt0${KmOxvzKRv^-OA>I7}=QJ6P&qR?-#XDdLE%sFd%` zIxKVE;;d9^taVTR!eCxqNpqd%5aL)u0!avvP(v9Nn26GMN&)jR=w&%l-1_E#Ig%qQvf zwiY=8`nG|K>$WEC0hc?#z>_BGqAmGodJ6^M{fxdT2lU?pfi-jfDCoDd;=UD z0;5IBUUzwSPiJrco@w>>1GU(4!UUvzS^xk5V@X6oRCt_YjEs!@&wvC(L_`?YuiwBx z3Shw{AuKFJxgp=be<#`yCZbi6;}Ueh$;k<4qq~wQLpV7(Q8c3Ssp1{_g~|8t-|>fu vh=>SLw&9C%mP3aQ5oHKIfYUq3&L;!_!LS?vf*UHQ00000NkvXXu0mjfeIoT5 literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_disabled_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_disabled_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..25d73de3e5e550cfe3b5360bd9cc8e073b259c2b GIT binary patch literal 156 zcmeAS@N?(olHy`uVBq!ia0vp^fFS$t6Wazs@(X{P_7( zvQNhHkONo3LDhrDkGtzo#RViynB*{^NpJ~15FlvUinD& zSL@01^X)&jbK2Y62X1@VT{O*mx}IPvhh4*E>l5eHSs03oWjI$zR=fmS$KdJe=d#Wz Gp$PzAg*#FJ literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_focused_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_focused_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..4adfb5ffb75ed12abaec159f5d710bdc8b12b824 GIT binary patch literal 557 zcmV+|0@D47P)4Tx0C=2zkv&MmKpe$iTcuiB9PA*XkfAzR5Eao)s#pXIrLEAagUO{|(4-+r zad8w}3l4rPRvlcNb#-tR1i=pwM<*vm7b)?7NufoI2gm(*ckglc4)8WAOfkB~0Yx?S zR6HhRbE|^?6#<0Mi($-)%+M0)#SC1>*F8LZy^HcJ_j7-akeoLd;1P)Dn5LV=8^qI_ zrp9@nILu0-Ongo}rqcz9AGt0${KmOxvzKRv^-OA>I7}=QJ6P&qR?-#XDdLE%sFd%` zIxKVE;;d9^taVTR!eCxqNpqd%5aL)u0!avvP(v9Nn26GMN&)jR=w&%l-1_E#Ig%qQvf zwiY=8`nG|K>$WEC0hc?#z>_BGqAmGodJ6^M{fxdT2lU?pfi-jfDCoDd;=UD z0;5IBUUzwSPiJrco@w>>1GU(4!UUvzS^xk5V@X6oRCt_Y^z`)n&wvC}R8$z&uiwBx z3Shw{AuKFJxgp=be<#`yCZbi6;}Ueh$;k<4qq~wQLpV7(Q8c3Ssp1{_g~|8t-|>fu vii!$Rw&9C%mP3aQ5oHKIfYUq3&L;!_pxoTX=G%L;CaVV@ZH}+X&sY8=+=l(Mhms#e<2eZr?bzF j{C(ielatUE+Rgapj07O~@ug+LwDmk7;IqUceL_}3VnRa;!q literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_selected_highlighted_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_selected_highlighted_dark.png.mcmeta new file mode 100644 index 000000000..e0f56a513 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/tab_menu_selected_highlighted_dark.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 12, + "height": 11, + "border": 4 + } + } +} From c431a244d8d9edb1f6e4421c7119f461ed1712f8 Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Wed, 12 Aug 2026 20:12:09 -0400 Subject: [PATCH 20/30] Switch surface's light variant to background_panel The dark theme variant already resolved to the same pixels either way (background_panel.png and dialog_background_opaque.png are byte- identical in the OreUIDarkM pack), but the light/stock versions of the two differ - switched per direct confirmation this is the intended panel asset. Its bevel (a 2px white highlight band under the top border, flat fill, a 2px darker shadow band above the bottom border) sits entirely within the nine-slice's fixed 4px edges, so the characteristic Bedrock "slight 3D look at the bottom" is preserved at any panel size rather than stretched away. Full live GameTest suite: 23/23 passing. --- .../textures/gui/sprites/bedrock/surface.png | Bin 161 -> 159 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface.png index 579ebc4b4f200ffa07bc6e9bfc016c12e11157fb..4f6757e4fa850b98068b4a4dff4be5f7fbc6fe13 100644 GIT binary patch delta 130 zcmV-|0Db?V0iOYoBztB_L_t(|ob8iQ3V<*SL*HbV+D<7u#qc2AvEqja>JaDlB^W3a zN<&go2Z#Yodn2j2%9+;T``KzQ-%A@Qwr`kg6^6d?J@e_QK;20tR4_kmj2-UDz kL5%Tr3iFZya16fl3aWT;E*z$Q%>V!Z07*qoM6N<$g0*Zoo&W#< delta 132 zcmV-~0DJ$R0igkqBzboIYh4E9oB=Si(I5=ZI@Xpy;DZ7Eh`LTdl02pK ms)5XG3v7OA+Q&bted7hlb0Kq_31dJ20000 Date: Wed, 12 Aug 2026 20:25:43 -0400 Subject: [PATCH 21/30] Use the user's hand-edited panel.png for the bedrock light theme's surface Same bevel convention as background_panel (top highlight band, flat fill, bottom shadow band within the fixed nine-slice edges), custom- built to fit the theme rather than sourced from a reference pack. Dark variant is unaffected (still background_panel). Full live GameTest suite: 23/23 passing. --- .../textures/gui/sprites/bedrock/surface.png | Bin 159 -> 141 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/surface.png index 4f6757e4fa850b98068b4a4dff4be5f7fbc6fe13..411612b031ccf98e39db0adc7949d3d95ec89578 100644 GIT binary patch delta 112 zcmbQw*vmLUB{Rg+#WAE}PV$t=lmDN0U`$9%NJy|K_#kjnsJaDlB^W3a zN<&go2Z#Yodn2j2%9+;T``KzQ-%A@Qwr`kg6^6d?J@e_QK;20tR4_kmj2-UDz kL5%Tr3iFZya16fl3aWT;E*z$Q%>V!Z07*qoM6N<$f~!P0iU0rr From f749f409fd99b49323e06aa450349774794fa24e Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Wed, 12 Aug 2026 20:34:01 -0400 Subject: [PATCH 22/30] Fix switch track/thumb rendering far too small (v4 rewrite regression) switch_track/switch_thumb are declared non-nineslice, and a non-nineslice sprite always renders at its own declared width/height regardless of what the caller (SwitchCore, enforcing SWITCH_MIN_WIDTH/HEIGHT = 34x18 and SWITCH_THUMB_SIZE = 14) actually requests. The original v3 script resized its toggle_off/on crops to match those exact sizes before saving; that resize step was dropped when v4 restructured the crop logic into off_pieces()/on_pieces() helpers for the light/dark variant loop. The result, confirmed via a real in-game screenshot: the track texture (native ~17x12) only covered part of the switch's real ~34x18 area, exposing the dialog panel behind it through the gap - looking like a half light/half dark switch with the thumb stuck mid-track instead of a normal-looking toggle. Restored the resize (nearest-neighbor, keeps the flat pixel-art look) in both off_pieces()/on_pieces(). Also includes test/TestScreen.kt's own Theme(type = "bedrock") switch (made directly by the user to screenshot-verify this fix), keeping the showcase screen previewing the bedrock theme. Full live GameTest suite: 23/23 passing. --- .../bedrock/dark/switch_thumb.json | 8 +++--- .../bedrock/dark/switch_track.json | 24 +++++++++--------- .../archie_themes/bedrock/switch_thumb.json | 8 +++--- .../archie_themes/bedrock/switch_track.json | 24 +++++++++--------- .../gui/sprites/bedrock/switch_thumb.png | Bin 500 -> 507 bytes .../gui/sprites/bedrock/switch_thumb_dark.png | Bin 507 -> 515 bytes .../sprites/bedrock/switch_thumb_disabled.png | Bin 99 -> 107 bytes .../bedrock/switch_thumb_disabled_dark.png | Bin 106 -> 114 bytes .../gui/sprites/bedrock/switch_track.png | Bin 542 -> 560 bytes .../sprites/bedrock/switch_track_clicked.png | Bin 117 -> 143 bytes .../switch_track_clicked_and_focused.png | Bin 116 -> 143 bytes .../switch_track_clicked_and_focused_dark.png | Bin 144 -> 178 bytes .../bedrock/switch_track_clicked_dark.png | Bin 144 -> 178 bytes .../gui/sprites/bedrock/switch_track_dark.png | Bin 557 -> 588 bytes .../sprites/bedrock/switch_track_disabled.png | Bin 141 -> 159 bytes .../bedrock/switch_track_disabled_dark.png | Bin 156 -> 185 bytes .../sprites/bedrock/switch_track_focused.png | Bin 547 -> 562 bytes .../bedrock/switch_track_focused_dark.png | Bin 557 -> 589 bytes .../kernelpanicsoft/archie/test/TestScreen.kt | 2 +- 19 files changed, 33 insertions(+), 33 deletions(-) diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_thumb.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_thumb.json index 287315c24..c8e4c6346 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_thumb.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_thumb.json @@ -3,11 +3,11 @@ "default": { "texture": "archie:bedrock/switch_thumb_dark", "texture_size": { - "width": 10, - "height": 12 + "width": 14, + "height": 14 }, - "width": 10, - "height": 12 + "width": 14, + "height": 14 }, "disabled": { "texture": "archie:bedrock/switch_thumb_disabled_dark" diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json index e81a82d57..41b849eb2 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json @@ -3,11 +3,11 @@ "default": { "texture": "archie:bedrock/switch_track_dark", "texture_size": { - "width": 17, - "height": 12 + "width": 34, + "height": 18 }, - "width": 17, - "height": 12 + "width": 34, + "height": 18 }, "focused": { "texture": "archie:bedrock/switch_track_focused_dark" @@ -15,20 +15,20 @@ "clicked": { "texture": "archie:bedrock/switch_track_clicked_dark", "texture_size": { - "width": 16, - "height": 12 + "width": 34, + "height": 18 }, - "width": 16, - "height": 12 + "width": 34, + "height": 18 }, "clicked_and_focused": { "texture": "archie:bedrock/switch_track_clicked_and_focused_dark", "texture_size": { - "width": 16, - "height": 12 + "width": 34, + "height": 18 }, - "width": 16, - "height": 12 + "width": 34, + "height": 18 }, "disabled": { "texture": "archie:bedrock/switch_track_disabled_dark" diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_thumb.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_thumb.json index 3e70e0c46..d1438deb8 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_thumb.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_thumb.json @@ -3,11 +3,11 @@ "default": { "texture": "archie:bedrock/switch_thumb", "texture_size": { - "width": 10, - "height": 12 + "width": 14, + "height": 14 }, - "width": 10, - "height": 12 + "width": 14, + "height": 14 }, "disabled": { "texture": "archie:bedrock/switch_thumb_disabled" diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_track.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_track.json index 9799705f5..293a7ae04 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_track.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_track.json @@ -3,11 +3,11 @@ "default": { "texture": "archie:bedrock/switch_track", "texture_size": { - "width": 17, - "height": 12 + "width": 34, + "height": 18 }, - "width": 17, - "height": 12 + "width": 34, + "height": 18 }, "focused": { "texture": "archie:bedrock/switch_track_focused" @@ -15,20 +15,20 @@ "clicked": { "texture": "archie:bedrock/switch_track_clicked", "texture_size": { - "width": 16, - "height": 12 + "width": 34, + "height": 18 }, - "width": 16, - "height": 12 + "width": 34, + "height": 18 }, "clicked_and_focused": { "texture": "archie:bedrock/switch_track_clicked_and_focused", "texture_size": { - "width": 16, - "height": 12 + "width": 34, + "height": 18 }, - "width": 16, - "height": 12 + "width": 34, + "height": 18 }, "disabled": { "texture": "archie:bedrock/switch_track_disabled" diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb.png index 8f05f7c8e3c890ffd17f782529d8031e21f98670..633d99d752eeed47b341432826aeaa548ec552b6 100644 GIT binary patch delta 113 zcmeyu{F_;^Gr-TCmrII^fq{Y7)59eQNb`X(2OE%-_t3q#QE>{Ro1v$RV@SoEx2Fwx z85DRJ4l@5|Udh!o@st+duE`%h-**TVJ>e6(k?GCmv)^)leK;KcfHm_Fm$a);=0%_> N44$rjF6*2UngAc{C9VJf delta 106 zcmey({DoPuGr-TCmrII^fq{Y7)59eQNQ2ls9Be=`I$LMKM#U+N_FA4Ujv*Cul2Z~A ze*FLczrOA9@qXh3=E-V4Nf8^fSeb>xB6Si%9wvlxFwDKjCH}6!SO}<{!PC{xWt~$( F696ewAX5MU diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb_dark.png index 0a3dbace574ec03147873a5850977706b0dc6f87..4d2eca23805d74c6a26276efeb859e1e9b8764fa 100644 GIT binary patch delta 121 zcmey(+{~ib8Q|y6%O%Cdz`(%k>ERLtr1?OYgAGW^d+1)=s5phu&(hPyF{ENn@|1}a z|H?~98~}k2&(2D-B{2$L)N(iF^X}C-AtNX#c`OU1@8Q|y6%O%Cdz`(%k>ERLtq(N*R4mKbeovpKAqv8}sH$zVs$B>FS$x|jy z{3|aZap1$Vv(jue4V>%WtZ+&a-E=@gNlB?f)acZS6aU1;#1_rF%3yYh?X{TPoDQHV N44$rjF6*2UngI9KBiaA} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb_disabled.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb_disabled.png index 9fa4f8471c8550a0b796e691e7e160b106743d71..f134ef2b18dc65c88b15432be784c09f6992d380 100644 GIT binary patch literal 107 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xaBTpB{kcv5PPa6szFyLW1*!$DD z(_oJ$NB?1m+O(RlZ<{Bxlx$yF#hr3o>ij}({Xg7)82lb_N%Prn`32O?;OXk;vd$@? F2>|3nA(;RG literal 99 zcmeAS@N?(olHy`uVBq!ia0vp^AT|#N8<337)>#0gv^-rLLn`JZrz9l&*kAv@u5ESr wdZPs9?CWb%Z`3E)GYf}B>Li3bObF#*=-$UAeuL@iY@jX%Pgg&ebxsLQ0MaHMEC2ui diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb_disabled_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb_disabled_dark.png index 5cfb1b3e7c6cbb662d03967a009b9fe2f5e4be06..75adbab7164b2fc769043326ed27c32c1e79fca1 100644 GIT binary patch literal 114 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xaOHUWakcv6UBBG*y^OKVg9{_?6 zA0NB3B{2$L)N(iF^X}C-AyZdZckA{E4K1x-^ZWbzQ+iWwb1=;JW-C8#!2JSf2!p4q KpUXO@geCy%lO&n| literal 106 zcmeAS@N?(olHy`uVBq!ia0vp^AT|#N8<337)>#0g3_V>OLn`JZi-?N;%}-7~{Ndwc zcea`a&h>9rI3O->5i+u{zDu#WAE}&f95@ zTn7v|Sj<)VKRfR`#{YBkwmiG2i$?=-#E^ySw(HrFYUzTFjUT{3R`lafW|E?sw0 Q2WT~er>mdKI;Vst0Gu^Ce*gdg delta 148 zcmdnMGLJ>EGr-TCmrII^fq{Y7)59eQNDBfn4+k5NY*7u{zfo}tV`hk_i(^Q|oaB^* zgdgV}6cZ8?5)uju3KVWPFthU+7zDoJ+Tp#S@y?wXk$+E4PWCibF*g@?7B(}KSkP6@ y)~^_GIOWrmlT+Ar6n$SbXx&RpS>UvZonilOk%{Xq-T8qQF?hQAxvXFS$tej5Kh8TaCL|^# zB&=A!Ui~(Mnpckc$%bZjej^#1iUNjMgC5=l#ju5Y_uA&h95~^S7{tafp_Sw2(HPc8 QKyw&8UHx3vIVCg!0PX}L=l}o! diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_and_focused.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_and_focused.png index a3f3879aa255754c69202301089fef3331bc4b00..cfd5720885bf5082cf66ca150ac641e879a6c8a3 100644 GIT binary patch literal 143 zcmeAS@N?(olHy`uVBq!ia0vp^NK1Y7B|@@z6cI&McJI%60X+wy6J8K=NE8J3~XP}48_c|rYIkoBK;!a lLd%I2&KDRTCuB-6GU)E&IiGXKWiikO22WQ%mvv4FO#tb}FwFn} literal 116 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~e!3HF=pW8M9DQiy`$B>FS$$$R;|6k9>#lX$X z%-meR-0~)yL12=#CtFT!Nmt_Q#lg%kni6vuxHod`eR?M2F_W9uWFv-oIeg7IDfK&m P#xQug`njxgN@xNA^=~7C diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_and_focused_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_and_focused_dark.png index 0530ea8ea9b339608e22383bf8e735b396c4791a..8774538e9ad2d74bc81d7dc7ac842b5613f8c75b 100644 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^NlM=fs1rCkV6hOZth}$W*lR zh)9%jSb?z#U!tSFS$x|jy{CnPkfrX9D z;Lgrs_SG`oY4*x-XRmXAoPGY&OHRe#ADPuoZBdpiSoGi_!|sF^QU{olBYY+tUmf7; u%DSKN$AN{;?FG9V{Mq(DTh7A6&R}p&VDhwWn`Z*8VDNPHb6Mw<&;$U#yEB3S diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_dark.png index e9b4608676c978697f981b35b38c7a20f1b1c2ca..e8d691064b0d9f65fc1fdc0d89c18d44d0779327 100644 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^N!yrv#zAiyUbjy9-P)NXyn-dd&Q>x*-QmCj&WQ(KPY`D1m-G{{k*R3s z5s@h6umWQfzC=gy4Koz{H!f6CQc`&>xku57)1u^sz|AUl|BeqQ1M*G;pXyCuU`W3u Wv}4`vzn_4PVDNPHb6Mw<&;$VDv_6mk literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~e!3HF=pW8M9sc=sh$B>FS$x|jy{CnPkfrX9D zU{B>|w$(D-Y4*x-XRmXAoPGY&OHRe#ADPuoZBdpiSoGi_!|sF^QU{olBYY+tUmf7$ u!m^+7$AN{;?FG9V{Mq(DTh7A6&M>b{U~&uRqGdoU7(8A5T-G@yGywp>YBKo% diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_dark.png index df3551868a0c9dc75721a5af91d9a0e06392ec07..1b64a8289114b7b26e78a44b946e2fdced936415 100644 GIT binary patch delta 195 zcmV;!06hP#1k3~>iBL{Q4GJ0x0000DNk~Le0000Y0000I2nGNE06skoZ?Pei0e^x? zL_t(|ob8o44!|G`1UFF{+ThReO7aFiMVVA?P{1;W;v!RWj=V-}A%q(ZhzI}>TP*)`9yu!myMa+n(qmgjVy^c$fP7`5)+J(}*nP`5I5;G#xnw6Wd x`_=HLHd{!GooG@@UpoM>R1xilS{O5q@&Ns=91NY*_xJz+002ovPDHLkV1iHSPBj1k delta 163 zcmX@ZvX(`$Gr-TCmrII^fq{Y7)59eQNDBfn4+k5NY*7u{zfo}tV@0y3i(^Q|oaCOK zoTC4j6E-n5*)C z(%yGv{g1`po-7Q?Qed&QSnkjwJAswGL7H{OH-$wajG?RyF`6&sw{)Z*Q_i39^LxC@ zxvm)wB3rDUYi^z;_7Nz))AU-?mtXJLT&G0(c2}@<$++FS$tej5Kh8TSCL|^# zBp4VNDBNz~7S}T{2zk-kF zTcav|tn82$EHsf(Y$b6Fc1Ev&bH*0+_J+@2qWeKntE kvop-oQo3u9bu3m+)Kx}Q$$Hl`pj#L`UHx3vIVCg!0G6Rag#Z8m literal 156 zcmeAS@N?(olHy`uVBq!ia0vp^fFS$t6Wazs@(X{P_7( zvQNhHkONo3LDhrDkGtzo#RViynB*{^NpJ~15FlvUinD& zSL@01^X)&jbK2Y62X1@VT{O*mx}IPvhh4*E>l5eHSs03oWjI$zR=fmS$KdJe=d#Wz Gp$PzAg*#FJ diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_focused.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_focused.png index 90e47218004f12b77fdd62c4bd99815ae9f45d4c..95a7ad06bfe15b6a96893e1798d388778e7f0e7e 100644 GIT binary patch delta 168 zcmZ3?vWZ2pGr-TCmrII^fq{Y7)59eQNGk!c5CO->5i+u{Oig#WAE}PV%4s z|NqywIWj~f@H}HV##ngZKI?>n1Sx^r8yf=K7ch$*@(MV3YZY(B)8^Yu8LtIqFmpt@ ztg$LF$XJwiMe#)f5H|>hT^EQx5^VkZP*=qyS*KissOt}ynvW^=G%rnLVb~O`Dqdu~ RMI2~3gQu&X%Q~loCICLjJd6MU delta 153 zcmdnQvY17&Gr-TCmrII^fq{Y7)59eQNDBfn4+k5NY*7u{zfo}tV_u}Ei(^Q|oa8_M z|NpON<6`7y27+QH<(q7OVn57p>zpL;QK%3TG#54V5-9Nfd5oTvd_& zLE_L4puYb%7R|WP@OSqE`#k3k)(~&?o+}>T7`|^+a+XE0Gr-TCmrII^fq{Y7)59eQNGk!c5CO->5i+vA)^U#WAE}&fBTG zc@HS?u*NSk*^y#AkRcWiNT>KJo~0Wds*}M z=Un`6y#3xf#7>;^nTy|A)KGa*pW7kJ?^b%QR=zE)S>j%w(-!@7^*YnxlO2|K=w0~X w`L}Wub;}lcYSzAGI1uEzMOrfJti>tj^0P8F9N)En03F8Q>FVdQ&MBb@0A~h9r2qf` delta 163 zcmX@hvX(`$Gr-TCmrII^fq{Y7)59eQNDBfn4+k5NY*7u{zfo}tV@0y3i(^Q|oa8TG zzWh1gz!DM?B5`f~{R0kM2?tdbb#z>ID&F5;uYOBFZBgi?$3i6sPM+jUU%Y#ghO>pG zWuVET&nq9v{%Sq>{r&xq?VVj+T!Gsjb{9=^Zs!wB<*;kGY<=RKIt#;71sMaaE_M!} Oy$qhNelF{r5}E)PSUoNP diff --git a/test/common/src/main/kotlin/net/kernelpanicsoft/archie/test/TestScreen.kt b/test/common/src/main/kotlin/net/kernelpanicsoft/archie/test/TestScreen.kt index 37ed13f38..004b1089b 100644 --- a/test/common/src/main/kotlin/net/kernelpanicsoft/archie/test/TestScreen.kt +++ b/test/common/src/main/kotlin/net/kernelpanicsoft/archie/test/TestScreen.kt @@ -66,7 +66,7 @@ class TestScreen(menu: TestMenu, playerInventory: Inventory, title: Component) : val layerManager = LocalLayerManager.current var syncedValue by observeProperty("test", "") val test = syncedValue ?: "" - Theme { + Theme(type = "bedrock") { Box(modifier = Modifier.width(contentWidth + 16)) { TabContainerPanel(contentWidth) { for (showcase in TestKind.entries) { From b7d7a6df4677514e63831f492fbde238b60d5cf8 Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Wed, 12 Aug 2026 21:32:47 -0400 Subject: [PATCH 23/30] Rework bedrock switch/slider art, add themed slider fill, fix dark-variant nine-slice mismatches Switch: - Replace the toggle_off/on splice with the user's own GIMP-extracted switch_track.png/switch_thumb(_dark).png. The track is a single static bicolor pill (green "I" side, gray ring side) shared by both themes and every track state - Switch.kt's own thumb-offset animation slides the thumb over whichever side doesn't match the current value, covering it, so there's nothing left to crop into separate on/off textures. Only the thumb differs between light/dark. - Delete the now-orphaned switch_track_clicked(_dark)/ switch_track_clicked_and_focused(_dark).png left behind by the old split. Slider fill: - Slider.kt drew its progress fill as a hardcoded solid-color fill() call. Replaced with a themed "slider_fill" composable (default/disabled states, looked up and drawn via drawThemeState like the track/thumb already are). - bedrock's slider_fill is Bedrock's own real slider_progress.png nine-slice asset; java's is a hand-authored 3x3 nine-slice solid preserving the previous hardcoded colors so appearance doesn't change for that theme. Dark-variant nine-slice mismatches (real bug, not slider_fill-specific): mcmeta_from_bedrock_json always read width/height/border from the light-only bedrock-samples json, but OreUIDarkM's dark-mode assets are sometimes exported at a different native resolution than their light counterpart (e.g. slider_button_default: 6x6 light vs 9x9 dark; button_borderless_dark: 4x4 vs 9x9). A mcmeta declaring the wrong source size produces bad nine-slice UVs without necessarily throwing at load - silent visual corruption, not a crash - which is why it wasn't caught earlier. Replaced with mcmeta_for_image(), which derives width/height from the actual saved image, preferring a same-named SAMPLES_DARK json sidecar when the user has provided one (real per-variant border), otherwise scaling the light json's border proportionally to the image's real size. Regenerated every affected mcmeta (button, slider, slider_handle, tab_menu, surface). Also delete four pre-existing orphaned java tab_*_clicked(.mcmeta) sprites - java's tab theme actually uses "_selected"/"_selected_highlighted" naming, so these were dead weight from before this session. Verified via full compile (core-common/fabric/neoforge, gametest-common/ neoforge) and the live :archie-gametest-neoforge:runGametestClient suite, 23/23 passing. Co-Authored-By: Claude Sonnet 5 --- .../archie/gui/composables/input/Slider.kt | 12 +++++++----- .../archie_themes/bedrock/dark/button.json | 8 ++++---- .../archie_themes/bedrock/dark/energy_bar.json | 4 ++-- .../archie_themes/bedrock/dark/fluid_tank.json | 4 ++-- .../bedrock/dark/progress_bar.json | 4 ++-- .../archie_themes/bedrock/dark/slider.json | 4 ++-- .../archie_themes/bedrock/dark/slider_fill.json | 16 ++++++++++++++++ .../bedrock/dark/slider_handle.json | 4 ++-- .../bedrock/dark/switch_track.json | 16 ++-------------- .../archie_themes/bedrock/slider_fill.json | 16 ++++++++++++++++ .../archie_themes/bedrock/switch_track.json | 16 ++-------------- .../archie_themes/java/dark/slider_fill.json | 16 ++++++++++++++++ .../archie/archie_themes/java/slider_fill.json | 16 ++++++++++++++++ .../bedrock/button_clicked_dark.png.mcmeta | 6 +++--- .../gui/sprites/bedrock/button_dark.png.mcmeta | 6 +++--- .../bedrock/button_highlighted_dark.png.mcmeta | 6 +++--- .../gui/sprites/bedrock/energy_bar_dark.png | Bin 78 -> 98 bytes .../sprites/bedrock/energy_bar_dark.png.mcmeta | 4 ++-- .../gui/sprites/bedrock/fluid_tank_dark.png | Bin 78 -> 98 bytes .../sprites/bedrock/fluid_tank_dark.png.mcmeta | 4 ++-- .../gui/sprites/bedrock/progress_bar_dark.png | Bin 78 -> 98 bytes .../bedrock/progress_bar_dark.png.mcmeta | 4 ++-- .../gui/sprites/bedrock/slider_dark.png | Bin 78 -> 98 bytes .../gui/sprites/bedrock/slider_dark.png.mcmeta | 6 +++--- .../gui/sprites/bedrock/slider_fill.png | Bin 0 -> 78 bytes .../slider_fill.png.mcmeta} | 6 +++--- .../gui/sprites/bedrock/slider_fill_dark.png | Bin 0 -> 108 bytes .../slider_fill_dark.png.mcmeta} | 6 +++--- .../sprites/bedrock/slider_fill_disabled.png | Bin 0 -> 78 bytes .../bedrock/slider_fill_disabled.png.mcmeta | 10 ++++++++++ .../bedrock/slider_fill_disabled_dark.png | Bin 0 -> 107 bytes .../slider_fill_disabled_dark.png.mcmeta | 10 ++++++++++ .../bedrock/slider_handle_dark.png.mcmeta | 6 +++--- .../gui/sprites/bedrock/switch_thumb.png | Bin 507 -> 138 bytes .../gui/sprites/bedrock/switch_thumb_dark.png | Bin 515 -> 137 bytes .../sprites/bedrock/switch_thumb_disabled.png | Bin 107 -> 147 bytes .../bedrock/switch_thumb_disabled_dark.png | Bin 114 -> 137 bytes .../gui/sprites/bedrock/switch_track.png | Bin 560 -> 224 bytes .../sprites/bedrock/switch_track_clicked.png | Bin 143 -> 0 bytes .../switch_track_clicked_and_focused.png | Bin 143 -> 0 bytes .../switch_track_clicked_and_focused_dark.png | Bin 178 -> 0 bytes .../bedrock/switch_track_clicked_dark.png | Bin 178 -> 0 bytes .../gui/sprites/bedrock/switch_track_dark.png | Bin 588 -> 224 bytes .../sprites/bedrock/switch_track_disabled.png | Bin 159 -> 220 bytes .../bedrock/switch_track_disabled_dark.png | Bin 185 -> 220 bytes .../sprites/bedrock/switch_track_focused.png | Bin 562 -> 222 bytes .../bedrock/switch_track_focused_dark.png | Bin 589 -> 222 bytes .../textures/gui/sprites/java/slider_fill.png | Bin 0 -> 78 bytes .../gui/sprites/java/slider_fill.png.mcmeta | 10 ++++++++++ .../gui/sprites/java/slider_fill_dark.png | Bin 0 -> 78 bytes .../sprites/java/slider_fill_dark.png.mcmeta | 10 ++++++++++ .../gui/sprites/java/slider_fill_disabled.png | Bin 0 -> 78 bytes .../java/slider_fill_disabled.png.mcmeta | 10 ++++++++++ .../sprites/java/slider_fill_disabled_dark.png | Bin 0 -> 78 bytes .../java/slider_fill_disabled_dark.png.mcmeta | 10 ++++++++++ .../gui/sprites/java/tab_game_clicked.png | Bin 163 -> 0 bytes .../sprites/java/tab_game_clicked.png.mcmeta | 15 --------------- .../java/tab_game_clicked_and_focused.png | Bin 162 -> 0 bytes .../tab_game_clicked_and_focused.png.mcmeta | 15 --------------- .../gui/sprites/java/tab_menu_clicked.png | Bin 192 -> 0 bytes .../java/tab_menu_clicked_and_focused.png | Bin 186 -> 0 bytes 61 files changed, 176 insertions(+), 104 deletions(-) create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_fill.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider_fill.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/java/dark/slider_fill.json create mode 100644 core/common/src/main/resources/assets/archie/archie_themes/java/slider_fill.json create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_fill.png rename core/common/src/main/resources/assets/archie/textures/gui/sprites/{java/tab_menu_clicked.png.mcmeta => bedrock/slider_fill.png.mcmeta} (54%) create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_fill_dark.png rename core/common/src/main/resources/assets/archie/textures/gui/sprites/{java/tab_menu_clicked_and_focused.png.mcmeta => bedrock/slider_fill_dark.png.mcmeta} (54%) create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_fill_disabled.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_fill_disabled.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_fill_disabled_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_fill_disabled_dark.png.mcmeta delete mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked.png delete mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_and_focused.png delete mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_and_focused_dark.png delete mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill_dark.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill_disabled.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill_disabled.png.mcmeta create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill_disabled_dark.png create mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill_disabled_dark.png.mcmeta delete mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_clicked.png delete mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_clicked.png.mcmeta delete mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_clicked_and_focused.png delete mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_clicked_and_focused.png.mcmeta delete mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_clicked.png delete mode 100644 core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_clicked_and_focused.png diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt index 591df13ba..a754611ea 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt @@ -156,8 +156,8 @@ fun SliderCore( } /** - * A standard themed horizontal slider, drawing a "slider" track and "slider_handle" thumb - * from the current theme, plus a solid-color fill up to the thumb. + * A standard themed horizontal slider, drawing a "slider" track, "slider_handle" thumb, and + * "slider_fill" progress fill up to the thumb, all from the current theme. * * @param value The current value, normalized/snapped via [snapSliderValue]. * @param onValueChange Called with the new normalized value on every drag/click update. @@ -181,6 +181,7 @@ fun Slider( val theme = LocalTheme.current val trackTheme = theme.getComposableTheme("slider") val thumbTheme = theme.getComposableTheme("slider_handle") + val fillTheme = theme.getComposableTheme("slider_fill") val sizeModifier = Modifier.sizeIn(minWidth = SLIDER_MIN_WIDTH, minHeight = SLIDER_MIN_HEIGHT) SliderCore( value = value, @@ -227,11 +228,12 @@ fun Slider( node.renderState = stateName val trackState = trackTheme.getState(stateName, variant) val thumbState = thumbTheme.getState(stateName, variant) - - val fillColor = if (enabled) 0xFF6BA8FF.toInt() else 0xFF5A5A5A.toInt() + val fillState = fillTheme.getState(WidgetState.resolve(fillTheme, variant, enabled = enabled), variant) drawThemeState(trackState, x, y, node.width, node.height) - fill(trackStart, trackY, fillEnd, trackY + SLIDER_TRACK_HEIGHT, fillColor) + if (fillEnd > trackStart) { + drawThemeState(fillState, trackStart, trackY, fillEnd - trackStart, SLIDER_TRACK_HEIGHT) + } val drawThumbWidth = (SLIDER_THUMB_WIDTH * thumbScale).roundToInt() val drawThumbHeight = (SLIDER_THUMB_HEIGHT * thumbScale).roundToInt() diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/button.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/button.json index d00d982f6..1587a3cd3 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/button.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/button.json @@ -3,11 +3,11 @@ "default": { "texture": "archie:bedrock/button_dark", "texture_size": { - "width": 4, - "height": 4 + "width": 9, + "height": 9 }, - "width": 4, - "height": 4 + "width": 9, + "height": 9 }, "focused": { "texture": "archie:bedrock/button_highlighted_dark" diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/energy_bar.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/energy_bar.json index 6e49955ce..d363a32d4 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/energy_bar.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/energy_bar.json @@ -3,8 +3,8 @@ "default": { "texture": "archie:bedrock/energy_bar_dark", "texture_size": { - "width": 3, - "height": 3 + "width": 5, + "height": 5 }, "width": 32, "height": 16 diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/fluid_tank.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/fluid_tank.json index b75421af0..02fe92792 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/fluid_tank.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/fluid_tank.json @@ -3,8 +3,8 @@ "default": { "texture": "archie:bedrock/fluid_tank_dark", "texture_size": { - "width": 3, - "height": 3 + "width": 5, + "height": 5 }, "width": 18, "height": 54 diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/progress_bar.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/progress_bar.json index 9299ac686..4b7ffe9f5 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/progress_bar.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/progress_bar.json @@ -3,8 +3,8 @@ "default": { "texture": "archie:bedrock/progress_bar_dark", "texture_size": { - "width": 3, - "height": 3 + "width": 5, + "height": 5 }, "width": 32, "height": 16 diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider.json index 09ed027c4..37eaad07c 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider.json @@ -3,8 +3,8 @@ "default": { "texture": "archie:bedrock/slider_dark", "texture_size": { - "width": 3, - "height": 3 + "width": 5, + "height": 5 }, "width": 200, "height": 20 diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_fill.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_fill.json new file mode 100644 index 000000000..8aa9b107e --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_fill.json @@ -0,0 +1,16 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/slider_fill_dark", + "texture_size": { + "width": 4, + "height": 4 + }, + "width": 8, + "height": 2 + }, + "disabled": { + "texture": "archie:bedrock/slider_fill_disabled_dark" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_handle.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_handle.json index c36dcccd6..33c070cee 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_handle.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_handle.json @@ -3,8 +3,8 @@ "default": { "texture": "archie:bedrock/slider_handle_dark", "texture_size": { - "width": 6, - "height": 6 + "width": 9, + "height": 9 }, "width": 8, "height": 20 diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json index 41b849eb2..7e6a7dffa 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json @@ -13,22 +13,10 @@ "texture": "archie:bedrock/switch_track_focused_dark" }, "clicked": { - "texture": "archie:bedrock/switch_track_clicked_dark", - "texture_size": { - "width": 34, - "height": 18 - }, - "width": 34, - "height": 18 + "texture": "archie:bedrock/switch_track_dark" }, "clicked_and_focused": { - "texture": "archie:bedrock/switch_track_clicked_and_focused_dark", - "texture_size": { - "width": 34, - "height": 18 - }, - "width": 34, - "height": 18 + "texture": "archie:bedrock/switch_track_focused_dark" }, "disabled": { "texture": "archie:bedrock/switch_track_disabled_dark" diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider_fill.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider_fill.json new file mode 100644 index 000000000..76eef1517 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider_fill.json @@ -0,0 +1,16 @@ +{ + "states": { + "default": { + "texture": "archie:bedrock/slider_fill", + "texture_size": { + "width": 3, + "height": 3 + }, + "width": 8, + "height": 2 + }, + "disabled": { + "texture": "archie:bedrock/slider_fill_disabled" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_track.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_track.json index 293a7ae04..e75968eb5 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_track.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_track.json @@ -13,22 +13,10 @@ "texture": "archie:bedrock/switch_track_focused" }, "clicked": { - "texture": "archie:bedrock/switch_track_clicked", - "texture_size": { - "width": 34, - "height": 18 - }, - "width": 34, - "height": 18 + "texture": "archie:bedrock/switch_track" }, "clicked_and_focused": { - "texture": "archie:bedrock/switch_track_clicked_and_focused", - "texture_size": { - "width": 34, - "height": 18 - }, - "width": 34, - "height": 18 + "texture": "archie:bedrock/switch_track_focused" }, "disabled": { "texture": "archie:bedrock/switch_track_disabled" diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/dark/slider_fill.json b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/slider_fill.json new file mode 100644 index 000000000..c118ee5af --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/dark/slider_fill.json @@ -0,0 +1,16 @@ +{ + "states": { + "default": { + "texture": "archie:java/slider_fill_dark", + "texture_size": { + "width": 3, + "height": 3 + }, + "width": 8, + "height": 2 + }, + "disabled": { + "texture": "archie:java/slider_fill_disabled_dark" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/archie_themes/java/slider_fill.json b/core/common/src/main/resources/assets/archie/archie_themes/java/slider_fill.json new file mode 100644 index 000000000..50c959ee0 --- /dev/null +++ b/core/common/src/main/resources/assets/archie/archie_themes/java/slider_fill.json @@ -0,0 +1,16 @@ +{ + "states": { + "default": { + "texture": "archie:java/slider_fill", + "texture_size": { + "width": 3, + "height": 3 + }, + "width": 8, + "height": 2 + }, + "disabled": { + "texture": "archie:java/slider_fill_disabled" + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_clicked_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_clicked_dark.png.mcmeta index eeca6296f..2375483bc 100644 --- a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_clicked_dark.png.mcmeta +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_clicked_dark.png.mcmeta @@ -2,9 +2,9 @@ "gui": { "scaling": { "type": "nine_slice", - "width": 4, - "height": 4, - "border": 1 + "width": 9, + "height": 9, + "border": 2 } } } diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_dark.png.mcmeta index eeca6296f..2375483bc 100644 --- a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_dark.png.mcmeta +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_dark.png.mcmeta @@ -2,9 +2,9 @@ "gui": { "scaling": { "type": "nine_slice", - "width": 4, - "height": 4, - "border": 1 + "width": 9, + "height": 9, + "border": 2 } } } diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted_dark.png.mcmeta index eeca6296f..2375483bc 100644 --- a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted_dark.png.mcmeta +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted_dark.png.mcmeta @@ -2,9 +2,9 @@ "gui": { "scaling": { "type": "nine_slice", - "width": 4, - "height": 4, - "border": 1 + "width": 9, + "height": 9, + "border": 2 } } } diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/energy_bar_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/energy_bar_dark.png index 7bb3e212768d44eb98aad1fce2997f487357a1ab..11d9a7632b0068052179f00f3193e2571c71c766 100644 GIT binary patch literal 98 zcmeAS@N?(olHy`uVBq!ia0vp^tRT$61|)m))t&+=O-~ockcv6UDG3Qb&Nr|{NB}`f tO3Fhf|9LiP2~Och4;*NSogg@SCWF&FR!((p!)HKE44$rjF6*2UngC(98lM0F literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~qMj~}Ar*6yr%asq_dElu503{! bD>H-sLB^;B)dw_z3K%?H{an^LB{Ts5UzHMq diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/energy_bar_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/energy_bar_dark.png.mcmeta index 5630bc1ac..8e54d6c19 100644 --- a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/energy_bar_dark.png.mcmeta +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/energy_bar_dark.png.mcmeta @@ -2,8 +2,8 @@ "gui": { "scaling": { "type": "nine_slice", - "width": 3, - "height": 3, + "width": 5, + "height": 5, "border": 1 } } diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/fluid_tank_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/fluid_tank_dark.png index 7bb3e212768d44eb98aad1fce2997f487357a1ab..11d9a7632b0068052179f00f3193e2571c71c766 100644 GIT binary patch literal 98 zcmeAS@N?(olHy`uVBq!ia0vp^tRT$61|)m))t&+=O-~ockcv6UDG3Qb&Nr|{NB}`f tO3Fhf|9LiP2~Och4;*NSogg@SCWF&FR!((p!)HKE44$rjF6*2UngC(98lM0F literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~qMj~}Ar*6yr%asq_dElu503{! bD>H-sLB^;B)dw_z3K%?H{an^LB{Ts5UzHMq diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/fluid_tank_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/fluid_tank_dark.png.mcmeta index 5630bc1ac..8e54d6c19 100644 --- a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/fluid_tank_dark.png.mcmeta +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/fluid_tank_dark.png.mcmeta @@ -2,8 +2,8 @@ "gui": { "scaling": { "type": "nine_slice", - "width": 3, - "height": 3, + "width": 5, + "height": 5, "border": 1 } } diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/progress_bar_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/progress_bar_dark.png index 7bb3e212768d44eb98aad1fce2997f487357a1ab..11d9a7632b0068052179f00f3193e2571c71c766 100644 GIT binary patch literal 98 zcmeAS@N?(olHy`uVBq!ia0vp^tRT$61|)m))t&+=O-~ockcv6UDG3Qb&Nr|{NB}`f tO3Fhf|9LiP2~Och4;*NSogg@SCWF&FR!((p!)HKE44$rjF6*2UngC(98lM0F literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~qMj~}Ar*6yr%asq_dElu503{! bD>H-sLB^;B)dw_z3K%?H{an^LB{Ts5UzHMq diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/progress_bar_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/progress_bar_dark.png.mcmeta index 5630bc1ac..8e54d6c19 100644 --- a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/progress_bar_dark.png.mcmeta +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/progress_bar_dark.png.mcmeta @@ -2,8 +2,8 @@ "gui": { "scaling": { "type": "nine_slice", - "width": 3, - "height": 3, + "width": 5, + "height": 5, "border": 1 } } diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_dark.png index 7bb3e212768d44eb98aad1fce2997f487357a1ab..11d9a7632b0068052179f00f3193e2571c71c766 100644 GIT binary patch literal 98 zcmeAS@N?(olHy`uVBq!ia0vp^tRT$61|)m))t&+=O-~ockcv6UDG3Qb&Nr|{NB}`f tO3Fhf|9LiP2~Och4;*NSogg@SCWF&FR!((p!)HKE44$rjF6*2UngC(98lM0F literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~qMj~}Ar*6yr%asq_dElu503{! bD>H-sLB^;B)dw_z3K%?H{an^LB{Ts5UzHMq diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_dark.png.mcmeta index 5630bc1ac..a41901d7c 100644 --- a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_dark.png.mcmeta +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_dark.png.mcmeta @@ -2,9 +2,9 @@ "gui": { "scaling": { "type": "nine_slice", - "width": 3, - "height": 3, - "border": 1 + "width": 5, + "height": 5, + "border": 2 } } } diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_fill.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_fill.png new file mode 100644 index 0000000000000000000000000000000000000000..5d525e294ff59947edde2a6bcf55a2aa27d40a47 GIT binary patch literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~qMj~}Ar*6yb22i1oM&M5;qhQ- aWoC%_$<*p8!7T(-z~JfX=d#Wzp$Py(WDyJi literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_clicked.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_fill.png.mcmeta similarity index 54% rename from core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_clicked.png.mcmeta rename to core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_fill.png.mcmeta index 12147d0c5..5630bc1ac 100644 --- a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_clicked.png.mcmeta +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_fill.png.mcmeta @@ -2,9 +2,9 @@ "gui": { "scaling": { "type": "nine_slice", - "width": 130, - "height": 24, - "border": 2 + "width": 3, + "height": 3, + "border": 1 } } } diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_fill_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_fill_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..d1f46de9e9b7d0d39ea92ee5b44fe11f25f3d0c9 GIT binary patch literal 108 zcmeAS@N?(olHy`uVBq!ia0vp^EFjFm1|(O0oL2{=j6Gc(Ln`LP9(3eA;K0FfaPPBy zGY-hjisV({$oN0kGW6>zh7`5%)HH^G${#!Lo_)t8AXxLkQt0_4jt6!??F^o-elF{r G5}E)$5F(`j literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_clicked_and_focused.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_fill_dark.png.mcmeta similarity index 54% rename from core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_clicked_and_focused.png.mcmeta rename to core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_fill_dark.png.mcmeta index 12147d0c5..eeca6296f 100644 --- a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_clicked_and_focused.png.mcmeta +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_fill_dark.png.mcmeta @@ -2,9 +2,9 @@ "gui": { "scaling": { "type": "nine_slice", - "width": 130, - "height": 24, - "border": 2 + "width": 4, + "height": 4, + "border": 1 } } } diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_fill_disabled.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_fill_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..887e5955f203d5a7b593a8e0c4fab5e4142fe4e9 GIT binary patch literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~qMj~}Ar*6yeLOsVoM&M5;qhQ- aWoGb|WKx}P@$58E0fVQjpUXO@geCwxpAl&Q literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_fill_disabled.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_fill_disabled.png.mcmeta new file mode 100644 index 000000000..5630bc1ac --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_fill_disabled.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 3, + "height": 3, + "border": 1 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_fill_disabled_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/slider_fill_disabled_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..7229d580cd8130dcbae99cbd41a74ebea69e6a05 GIT binary patch literal 107 zcmeAS@N?(olHy`uVBq!ia0vp^EFjFm1|(O0oL2{=j67W&Ln`LP9^5F%puoYf@%rW@ zmSsu*8S9w(!m1~ShK92$7+Y%HtdKAQ~AM z7#Kc%`b?BYRu+gBCX(ET1V%N|LL&NjN~SA0LsD&G{NnM Q5&!@I07*qoM6N<$f;HAELI3~& delta 482 zcmV<80UiE|0s8}xBYy#fX+uL$Nkc;*P;zf(X>4Tx0C=2zkv&MmKpe$iTcuiB9PA*X zkfAzR5Eao)s#pXIrLEAagUO{|(4-+rad8w}3l4rPRvlcNb#-tR1i=pwM<*vm7b)?7 zNufoI2gm(*ckglc4)8WAOfkB~0Yx?SR6HhRbE|^?6#<0Mi+^FvipBS6O$JaeP ze7%eEEcbJNj*y%;7~m0z=a{CO#2duZo2JHjpE%4)qD*{FJf_nHi66NxIsC@CXtS4R zhV@Kpo;XY_6gybzU{=x<;wj>Ytf-Xl%{nY|-r}rOYOHln{=#5hT}gAD<`CjoLIOz$ zkWfPz6_|+9s(+GVAVvEz7yqE`Pm)U_R~d{P3#dYYX#2tc;CHu1VRFJr3dVr$7t8t> z0sOl_vu;`6$ClMR0esKEmD=`K8^Fvb>Gif2IRg5&fs5<5ChY;2JHWt`ChDRs`Dl6z z1>pURz9|Rv-vWU(r?=KVP9J~_b+vp092^3pMao`xd0Tf+XK(+WY4!I5wb*jP1f+ag z0000nNkl$O~&Gu~i@WWT& Y1!=+x8$}Xn(EtDd07*qoM6N<$f{A?HhyVZp diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb_dark.png index 4d2eca23805d74c6a26276efeb859e1e9b8764fa..c471d07d43de5680eb4ae53bbee8da91f893301c 100644 GIT binary patch delta 109 zcmZo>>13RsoW>B~>EaktF(-M-#EE~;JFq1rCL|4Tx0C=2zkv&MmKpe$iTcuiB9PA*X zkfAzR5Eao)s#pXIrLEAagUO{|(4-+rad8w}3l4rPRvlcNb#-tR1i=pwM<*vm7b)?7 zNufoI2gm(*ckglc4)8WAOfkB~0Yx?SR6HhRbE|^?6#<0Mi+^FvipBS6O$JaeP ze7%eEEcbJNj*y%;7~m0z=a{CO#2duZo2JHjpE%4)qD*{FJf_nHi66NxIsC@CXtS4R zhV@Kpo;XY_6gybzU{=x<;wj>Ytf-Xl%{nY|-r}rOYOHln{=#5hT}gAD<`CjoLIOz$ zkWfPz6_|+9s(+GVAVvEz7yqE`Pm)U_R~d{P3#dYYX#2tc;CHu1VRFJr3dVr$7t8t> z0sOl_vu;`6$ClMR0esKEmD=`K8^Fvb>Gif2IRg5&fs5<5ChY;2JHWt`ChDRs`Dl6z z1>pURz9|Rv-vWU(r?=KVP9J~_b+vp092^3pMao`xd1H4^XK(+WY4!I5wb*jP1f+ag z0000vNkllL3$)YnZEG64lR&l2s hmpZ?YTmKLD9|pfiT+)2@TYfPBfv2mV%Q~loCII0CA143+ diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb_disabled_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb_disabled_dark.png index 75adbab7164b2fc769043326ed27c32c1e79fca1..6ba628811e68992aa9fb09cbf53a164341cfa692 100644 GIT binary patch delta 107 zcmXTwWSpRq8sO>T7*a7OSwvLy?|BEdgv5k|gbf=vDtca?sO+vFB`F~xVORIZV~Sc` zT^$<;s7+eq#I#r{bD^MI|Arrmo{yJ$Pxsh1(P(+yuP>b`y$mHzJa>P`YtLW+0#8>z Jmvv4FO#nT4C=37q delta 84 zcmeBVESjL=XX)wU7*a7OSwvLyZ+>#};R8VM;p1a>wj@U3i(2lceBQk}CuHjC>TcaW mp`oSqYkq%!e@bu4Z4QR{-fZQ^4Y*%00D-5gpUXO@geCykRU#|^ diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track.png index e4e78a27591bcf80ed647f547bdaede173e49fa9..ecf0c88b5d986c72b1dee2d15bafc6795488f157 100644 GIT binary patch delta 197 zcmV;$06PD$1mFRXBYyy=NklaH=I2JugITklyMry}0 zAR&)s%F?0;IYsioo1$3x{<57Yp|1BM4u}B9Hkbu&%}7;kicAc{gH@5jg2c3Vc6Jd} zHJ=p`06!IRV@&xT#3+@-cb?pV=jeO!&y000000NkvXXu0mjfdaYVw delta 536 zcmV+z0_XkU0k8y+BYy#fX+uL$Nkc;*P;zf(X>4Tx0C=2zkv&MmKpe$iTcuiB9PA*X zkfAzR5Eao)s#pXIrLEAagUO{|(4-+rad8w}3l4rPRvlcNb#-tR1i=pwM<*vm7b)?7 zNufoI2gm(*ckglc4)8WAOfkB~0Yx?SR6HhRbE|^?6#<0Mi+^FvipBS6O$JaeP ze7%eEEcbJNj*y%;7~m0z=a{CO#2duZo2JHjpE%4)qD*{FJf_nHi66NxIsC@CXtS4R zhV@Kpo;XY_6gybzU{=x<;wj>Ytf-Xl%{nY|-r}rOYOHln{=#5hT}gAD<`CjoLIOz$ zkWfPz6_|+9s(+GVAVvEz7yqE`Pm)U_R~d{P3#dYYX#2tc;CHu1VRFJr3dVr$7t8t> z0sOl_vu;`6$ClMR0esKEmD=`K8^Fvb>Gif2IRg5&fs5<5ChY;2JHWt`ChDRs`Dl6z z1>pURz9|Rv-vWU(r?=KVP9J~_b+vp092^3pMao`xd4G3LXK(+WY4!I5wb*jP1f+ag z0001HNkl6cL@JuE{I_0000K1Y7B|@@z6cI&McJI%60X+wy6J8K=NE8J3~XP}48_c|rYIkoBK;!a lLd%I2&KDRTCuB-6GU)E&IiGXKWiikO22WQ%mvv4FO#tb}FwFn} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_and_focused_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_clicked_and_focused_dark.png deleted file mode 100644 index 8774538e9ad2d74bc81d7dc7ac842b5613f8c75b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^NlM=fs1rCkV6hOZth}$W*lR zh)9%jSb?z#U!tS!yrv#zAiyUbjy9-P)NXyn-dd&Q>x*-QmCj&WQ(KPY`D1m-G{{k*R3s z5s@h6umWQfzC=gy4Koz{H!f6CQc`&>xku57)1u^sz|AUl|BeqQ1M*G;pXyCuU`W3u Wv}4`vzn_4PVDNPHb6Mw<&;$VDv_6mk diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_dark.png index 1b64a8289114b7b26e78a44b946e2fdced936415..ecf0c88b5d986c72b1dee2d15bafc6795488f157 100644 GIT binary patch delta 197 zcmV;$06PE71mFRXBYyy=NklaH=I2JugITklyMry}0 zAR&)s%F?0;IYsioo1$3x{<57Yp|1BM4u}B9Hkbu&%}7;kicAc{gH@5jg2c3Vc6Jd} zHJ=p`06!IRV@&xT#3+@-cb?pV=jeO!&y000000NkvXXu0mjfkU3g@ delta 564 zcmV-40?Ym20n7xDBYy#fX+uL$Nkc;*P;zf(X>4Tx0C=2zkv&MmKpe$iTcuiB9PA*X zkfAzR5Eao)s#pXIrLEAagUO{|(4-+rad8w}3l4rPRvlcNb#-tR1i=pwM<*vm7b)?7 zNufoI2gm(*ckglc4)8WAOfkB~0Yx?SR6HhRbE|^?6#<0Mi+^FvipBS6O$JaeP ze7%eEEcbJNj*y%;7~m0z=a{CO#2duZo2JHjpE%4)qD*{FJf_nHi66NxIsC@CXtS4R zhV@Kpo;XY_6gybzU{=x<;wj>Ytf-Xl%{nY|-r}rOYOHln{=#5hT}gAD<`CjoLIOz$ zkWfPz6_|+9s(+GVAVvEz7yqE`Pm)U_R~d{P3#dYYX#2tc;CHu1VRFJr3dVr$7t8t> z0sOl_vu;`6$ClMR0esKEmD=`K8^Fvb>Gif2IRg5&fs5<5ChY;2JHWt`ChDRs`Dl6z z1>pURz9|Rv-vWU(r?=KVP9J~_b+vp092^3pMao`xd4G3LXK(+WY4!I5wb*jP1f+ag z0001jNklSZcxB7hvFhra*n)4Y$1dj4TuN; z5aZQaHq4>j;QhJ61N)LwJ5X07=e)whPDRX!sH2f^&b^LHkxmm~f!c-AyqRczkrFc^ z)0&l=ur2%5@TWFgNQ<3lQc7Pt0I*aM?S@)2j`9Hgt{e=V)c5!R0000T8wHs5AfgljRRsWCDd``yh-bzlZAoEMf!PEqyPW_07*qoM6N<$g4k0vR{#J2 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_disabled_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_disabled_dark.png index dcc3b85ce8b7cde8efe39df1e855e9e350ff8002..d0ecb86d33923f53ccf60e65747c8576edcaace2 100644 GIT binary patch delta 192 zcmV;x06+h^0o(zQB!8nxL_t(|oMRLf75&dJ2mlKM0|SF)h(6f{9A9&hfsc=mB1e4s z^ofCqA`_?xSYV7#@4k_3$j_fY$=1ux&d$KVz%Y11WY9Q-1)pSnMk@mY0|P_jjB2tB zhXp$f;EO`M4xt3Fv$JDZj7@JX9BjNN-CNNune~1B3JS~ z`5AGflx|FjF#-T`&a-9PT+$6hWZ{9+ay`RRPZ!6KiaE(4AtArc8*ngiGcz+cKa^72#A_k{soyasrbo$rPtDJ+ z#v1Ve$qff2svpWK^pn+F8WWrv(Jk`a4j z(4f!t(W*u0z}d6CiH2r|5)vkX#;*iQ95$PL`uaNjm1OKt!F delta 538 zcmV+#0_FYQ0kQ;;BYy#fX+uL$Nkc;*P;zf(X>4Tx0C=2zkv&MmKpe$iTcuiB9PA*X zkfAzR5Eao)s#pXIrLEAagUO{|(4-+rad8w}3l4rPRvlcNb#-tR1i=pwM<*vm7b)?7 zNufoI2gm(*ckglc4)8WAOfkB~0Yx?SR6HhRbE|^?6#<0Mi+^FvipBS6O$JaeP ze7%eEEcbJNj*y%;7~m0z=a{CO#2duZo2JHjpE%4)qD*{FJf_nHi66NxIsC@CXtS4R zhV@Kpo;XY_6gybzU{=x<;wj>Ytf-Xl%{nY|-r}rOYOHln{=#5hT}gAD<`CjoLIOz$ zkWfPz6_|+9s(+GVAVvEz7yqE`Pm)U_R~d{P3#dYYX#2tc;CHu1VRFJr3dVr$7t8t> z0sOl_vu;`6$ClMR0esKEmD=`K8^Fvb>Gif2IRg5&fs5<5ChY;2JHWt`ChDRs`Dl6z z1>pURz9|Rv-vWU(r?=KVP9J~_b+vp092^3pMao`xd4G3LXK(+WY4!I5wb*jP1f+ag z0001JNklXqHv4ByBpaT}dN>IVtsttJM zgWCdV>kya&2w6m|I&?5-qGr?~=wQ?#=ztMc*AQF8Q#<>@ig=P8LTxZw*Wdz!#vzP@ crC|gBvJ+D!7jiSS7XSbN07*qoM6N<$g1#*EVE_OC diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_focused_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_focused_dark.png index 9da733add148bfd113a9049f1ee8c26d5449e0d6..9d97103840f9d322af5823e036f4e0b374a59fcd 100644 GIT binary patch delta 195 zcmX@ha*uI>ay`RRPZ!6KiaE(4AtArc8*ngiGcz+cKa^72#A_k{soyasrbo$rPtDJ+ z#v1Ve$qff2svpWK^pn+F8WWrv(Jk`a4j z(4f!t(W*u0z}d6CiH2r|5)vkX#;*iQ95$PL`uaNjm14Tx0C=2zkv&MmKpe$iTcuiB9PA*X zkfAzR5Eao)s#pXIrLEAagUO{|(4-+rad8w}3l4rPRvlcNb#-tR1i=pwM<*vm7b)?7 zNufoI2gm(*ckglc4)8WAOfkB~0Yx?SR6HhRbE|^?6#<0Mi+^FvipBS6O$JaeP ze7%eEEcbJNj*y%;7~m0z=a{CO#2duZo2JHjpE%4)qD*{FJf_nHi66NxIsC@CXtS4R zhV@Kpo;XY_6gybzU{=x<;wj>Ytf-Xl%{nY|-r}rOYOHln{=#5hT}gAD<`CjoLIOz$ zkWfPz6_|+9s(+GVAVvEz7yqE`Pm)U_R~d{P3#dYYX#2tc;CHu1VRFJr3dVr$7t8t> z0sOl_vu;`6$ClMR0esKEmD=`K8^Fvb>Gif2IRg5&fs5<5ChY;2JHWt`ChDRs`Dl6z z1>pURz9|Rv-vWU(r?=KVP9J~_b+vp092^3pMao`xd4G3LXK(+WY4!I5wb*jP1f+ag z0001kNklBZZsew z06>gaYuPY|c7yll3J>f{PVGQlk(~1i4?7hxBchH*!a4UkE=4*`gav9BO7mu-`9(_1 zh)io%ZZ5*^SHqv$Y#}XnqDd)z?Et`1MYJ1g%{a;fcg-9;2=^=a00000NkvXXu0mjf DMAiRr diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill.png new file mode 100644 index 0000000000000000000000000000000000000000..76e17d6567aa4ae8e8642f81e51110b61c4641cc GIT binary patch literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~qMj~}Ar*6y&%}NFZ_g}ZV5q<- a!pD$b&n(WFrDOqAz~JfX=d#Wzp$Pyx4-jnt literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill.png.mcmeta new file mode 100644 index 000000000..5630bc1ac --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 3, + "height": 3, + "border": 1 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..ee040cfa6b850c6c5d6929314e5c52b4b727061c GIT binary patch literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~qMj~}Ar*6yuN3+HKF`4F!{fow a%FN)nhw+BZ^$H)L0tQc4KbLh*2~7ZD+7i3~ literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill_dark.png.mcmeta new file mode 100644 index 000000000..5630bc1ac --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill_dark.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 3, + "height": 3, + "border": 1 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill_disabled.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..934be3039fb4dd9850f3c9640b67ee6eb0734a7f GIT binary patch literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~qMj~}Ar*6yd%C)Qoo8V6;qhQ- ZWoGcIXA=Is>o!OMgQu&X%Q~loCIDw^6Gs35 literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill_disabled.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill_disabled.png.mcmeta new file mode 100644 index 000000000..5630bc1ac --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill_disabled.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 3, + "height": 3, + "border": 1 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill_disabled_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill_disabled_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..336d44ce1972c34d54a143705bb63465d46f15dd GIT binary patch literal 78 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~qMj~}Ar*6yLjnVTo@Ze7;qhQ- aWo9rIWh|T?&1D2sz~JfX=d#Wzp$PyleGpdw literal 0 HcmV?d00001 diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill_disabled_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill_disabled_dark.png.mcmeta new file mode 100644 index 000000000..5630bc1ac --- /dev/null +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/slider_fill_disabled_dark.png.mcmeta @@ -0,0 +1,10 @@ +{ + "gui": { + "scaling": { + "type": "nine_slice", + "width": 3, + "height": 3, + "border": 1 + } + } +} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_clicked.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_clicked.png deleted file mode 100644 index c7f9552aa298a3c2b389ba7c1a6f2ccbf667fe78..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 163 zcmeAS@N?(olHy`uVBq!ia0vp^Qb4T0!3HFGR%fsRsVq+y$B>FSZ?7409WdZ%eR%(l zd{O@sxn~h&nz<(~xwapwp1k10!pX;GE7+Hb2~K=J<7V;}EtUg!AMTqn_d=95@2kDc z2G5tg?e%%$^IJ|(#bbhkV-pLf&=>XL diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_clicked.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_clicked.png.mcmeta deleted file mode 100644 index 49e89779d..000000000 --- a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_clicked.png.mcmeta +++ /dev/null @@ -1,15 +0,0 @@ -{ - "gui": { - "scaling": { - "type": "nine_slice", - "width": 26, - "height": 32, - "border": { - "left": 7, - "top": 8, - "right": 7, - "bottom": 8 - } - } - } -} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_clicked_and_focused.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_clicked_and_focused.png deleted file mode 100644 index 9c5c6eaae327db015a9a316d3e80b089a09f9c39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 162 zcmeAS@N?(olHy`uVBq!ia0vp^Qb4T0!3HFGR%fsRsZ387$B>FSZ?7409WdZ%eRyBS zPTjLFWnJ?0OKm!_B^rCoIl1JmGWT$PN=_FP{Mk3t)@`*D!yVao-ZwedZoRbNo;5?U z->rKmT+VHIFX-6B!YQQUF+stxzG2n9hqi)&`!7l`GZcUSHMM~|sY+#zp}zMoprs6+ Lu6{1-oD!M<)>}BR diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_clicked_and_focused.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_clicked_and_focused.png.mcmeta deleted file mode 100644 index 49e89779d..000000000 --- a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_game_clicked_and_focused.png.mcmeta +++ /dev/null @@ -1,15 +0,0 @@ -{ - "gui": { - "scaling": { - "type": "nine_slice", - "width": 26, - "height": 32, - "border": { - "left": 7, - "top": 8, - "right": 7, - "bottom": 8 - } - } - } -} diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_clicked.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/java/tab_menu_clicked.png deleted file mode 100644 index 1c4eb8bf1f7fb9afc65b10d1a82cbec5d8bf21a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^O+YNc!3HGF{;A#oQth5Djv*Cu-rm~C+Z-Uka?#So zJ>l>JD?{QtY`}>Q)*_Erl|N3m9emujiJfGq8 z7lFU*&H@P>TtpIGyYjB>iL6=n`~S~*oA*C^mT`gc17l^o-~-9*3lcylF?hQAxvX From 236ac244cdf754493571f5414843c26bd8df1d4a Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Wed, 12 Aug 2026 21:43:32 -0400 Subject: [PATCH 24/30] Document the ResourceLocation plus operators These three operator fun plus overloads (a no-separator counterpart to the existing div operators above them) were added earlier but left undocumented. Co-Authored-By: Claude Sonnet 5 --- .../net/kernelpanicsoft/archie/util/ResourceLocation.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/util/ResourceLocation.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/util/ResourceLocation.kt index a188af77f..d9c8182b8 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/util/ResourceLocation.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/util/ResourceLocation.kt @@ -16,4 +16,11 @@ operator fun ResourceLocation.div(other: String): ResourceLocation = withSuffix( /** Appends `/` plus [other]'s path (namespace of [other] is ignored) to this location's path. */ operator fun ResourceLocation.div(other: ResourceLocation): ResourceLocation = withSuffix("/${other.path}") /** Prepends `this/` to [other]'s path, keeping [other]'s namespace. */ -operator fun String.div(other: ResourceLocation): ResourceLocation = other.withPrefix("$this/") \ No newline at end of file +operator fun String.div(other: ResourceLocation): ResourceLocation = other.withPrefix("$this/") + +/** Appends [other] directly to this location's path with no separator, e.g. `loc + "_dark"`. */ +operator fun ResourceLocation.plus(other: String): ResourceLocation = withSuffix(other) +/** Appends [other]'s path directly to this location's path with no separator (namespace of [other] is ignored). */ +operator fun ResourceLocation.plus(other: ResourceLocation): ResourceLocation = withSuffix(other.path) +/** Prepends `this` directly to [other]'s path with no separator, keeping [other]'s namespace. */ +operator fun String.plus(other: ResourceLocation): ResourceLocation = other.withPrefix(this) \ No newline at end of file From 3ef0cfc33968e6b4e767458e126a7e65aed3bdfd Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Wed, 12 Aug 2026 22:03:17 -0400 Subject: [PATCH 25/30] Fix bedrock switch/slider proportions and dark button banding via per-theme sizing Switch/Slider now let a theme override the fixed pixel sizes Switch.kt/ Slider.kt previously hardcoded for every theme: - ComposableTheme.minSize (already used by Button for its clickable area) is now also read by Switch.kt for the track/thumb size, and by Slider.kt for the overall size/thumb size - falling back to the old constants when a theme doesn't declare one. - ComposableTheme.contentPadding.horizontal now also controls how far a Switch's thumb sits from the track's edge (was a hardcoded 2px gap). Java is unaffected (declares neither, so keeps the old defaults exactly). Bedrock now declares: - switch_track/switch_thumb: min_size matching the user's GIMP-extracted art at its own native pixel size (28x13/15x15), used unresized - previously these were force-resized into Java's proportions (34x18/ 14x14), visibly distorting a source that was already correctly sized. content_padding: {horizontal: 0} so the thumb sits flush in the track's corner, matching how the source art was designed to be read. - slider_handle: min_size 12x16, much closer to the source knob's own compact/near-square aspect than Slider.kt's default SLIDER_THUMB_WIDTH/ HEIGHT (8x20, a tall thin pill tuned for Java's own vanilla-style art), which was stretching Bedrock's compact nine-slice knob into a visibly distorted shape. Also fixes a real dark-theme button rendering bug (visible horizontal banding across the button face): OreUIDarkM's 9x9 button recolor has a 1px top bevel, a flat face, and a bottom edge that's a 1px bevel for the resting/hover art but a 1px bevel + 2px drop-shadow for those same states (vs. a flush 1px bevel with no shadow for the pressed art). The generic proportionally-scaled border (derived from the light 4x4 source, which has no such shadow band) swallowed part of the flat face into the tiled bottom edge, so that discontinuity repeated as visible banding once stretched across a real button's height. Declared an explicit per-edge border for this specific asset instead of relying on the generic size-mismatch heuristic. TestScreen.kt: mode = "dark" added (user's own testing change, previewing bedrock's dark variant). Verified via full compile (core-common/fabric/neoforge, gametest-common/ neoforge) and the live :archie-gametest-neoforge:runGametestClient suite, 23/23 passing (one run hit 4 failures, all the same pre-existing "stale render state" harness race documented in engram - unrelated composables, no Kotlin interaction-timing code touched here, and a clean rerun immediately after confirmed it). Co-Authored-By: Claude Sonnet 5 --- .../archie/gui/composables/input/Slider.kt | 30 ++++++++++------ .../archie/gui/composables/input/Switch.kt | 33 +++++++++++++----- .../bedrock/dark/slider_handle.json | 8 +++-- .../bedrock/dark/switch_thumb.json | 8 ++--- .../bedrock/dark/switch_track.json | 15 +++++--- .../archie_themes/bedrock/slider_handle.json | 8 +++-- .../archie_themes/bedrock/switch_thumb.json | 8 ++--- .../archie_themes/bedrock/switch_track.json | 15 +++++--- .../bedrock/button_clicked_dark.png.mcmeta | 7 +++- .../sprites/bedrock/button_dark.png.mcmeta | 7 +++- .../button_highlighted_dark.png.mcmeta | 7 +++- .../gui/sprites/bedrock/switch_thumb.png | Bin 138 -> 138 bytes .../gui/sprites/bedrock/switch_thumb_dark.png | Bin 137 -> 138 bytes .../sprites/bedrock/switch_thumb_disabled.png | Bin 147 -> 148 bytes .../bedrock/switch_thumb_disabled_dark.png | Bin 137 -> 138 bytes .../gui/sprites/bedrock/switch_track.png | Bin 224 -> 203 bytes .../gui/sprites/bedrock/switch_track_dark.png | Bin 224 -> 203 bytes .../sprites/bedrock/switch_track_disabled.png | Bin 220 -> 196 bytes .../bedrock/switch_track_disabled_dark.png | Bin 220 -> 196 bytes .../sprites/bedrock/switch_track_focused.png | Bin 222 -> 199 bytes .../bedrock/switch_track_focused_dark.png | Bin 222 -> 199 bytes .../kernelpanicsoft/archie/test/TestScreen.kt | 2 +- 22 files changed, 105 insertions(+), 43 deletions(-) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt index a754611ea..c88b21857 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt @@ -15,6 +15,7 @@ import net.kernelpanicsoft.archie.gui.layout.Alignment import net.kernelpanicsoft.archie.gui.layout.BoxMeasurePolicy import net.kernelpanicsoft.archie.gui.layout.Layout import net.kernelpanicsoft.archie.gui.layout.Renderer +import net.kernelpanicsoft.archie.gui.layout.Size import net.kernelpanicsoft.archie.gui.modifiers.Modifier import net.kernelpanicsoft.archie.gui.modifiers.input.PointerEventType import net.kernelpanicsoft.archie.gui.modifiers.input.focusable @@ -35,8 +36,10 @@ import org.lwjgl.glfw.GLFW import kotlin.math.roundToInt import kotlin.time.Duration.Companion.milliseconds +/** Fallback overall size when the "slider" theme doesn't declare its own [ComposableTheme.minSize]. */ private const val SLIDER_MIN_WIDTH = 96 private const val SLIDER_MIN_HEIGHT = 20 +/** Fallback thumb size when the "slider_handle" theme doesn't declare its own [ComposableTheme.minSize]. */ private const val SLIDER_THUMB_WIDTH = 8 private const val SLIDER_THUMB_HEIGHT = 20 private const val SLIDER_TRACK_HEIGHT = 2 @@ -182,7 +185,14 @@ fun Slider( val trackTheme = theme.getComposableTheme("slider") val thumbTheme = theme.getComposableTheme("slider_handle") val fillTheme = theme.getComposableTheme("slider_fill") - val sizeModifier = Modifier.sizeIn(minWidth = SLIDER_MIN_WIDTH, minHeight = SLIDER_MIN_HEIGHT) + // A theme's handle art isn't always native to SLIDER_THUMB_WIDTH/HEIGHT's proportions + // (tuned for the default look, a tall thin pill) - min_size on the "slider"/"slider_handle" + // theme entries lets a theme declare its own real dimensions instead of getting + // force-stretched into ones it wasn't designed for (a compact/roughly-square handle nine-sliced + // into a tall thin pill looks visibly distorted). + val sliderSize = trackTheme.minSize ?: Size(SLIDER_MIN_WIDTH, SLIDER_MIN_HEIGHT) + val thumbSize = thumbTheme.minSize ?: Size(SLIDER_THUMB_WIDTH, SLIDER_THUMB_HEIGHT) + val sizeModifier = Modifier.sizeIn(minWidth = sliderSize.width, minHeight = sliderSize.height) SliderCore( value = value, onValueChange = onValueChange, @@ -212,17 +222,17 @@ fun Slider( partialTick: Float, ) = guiGraphics { val trackY = y + (node.height - SLIDER_TRACK_HEIGHT) / 2 - val trackStart = x + (SLIDER_THUMB_WIDTH / 2) - val trackEnd = x + node.width - (SLIDER_THUMB_WIDTH / 2) + val trackStart = x + (thumbSize.width / 2) + val trackEnd = x + node.width - (thumbSize.width / 2) val availableTrack = (trackEnd - trackStart).coerceAtLeast(1) val fillEnd = trackStart + (availableTrack * normalizedValue).roundToInt() val thumbX = resolveSliderThumbX( - rawThumbX = fillEnd - (SLIDER_THUMB_WIDTH / 2), + rawThumbX = fillEnd - (thumbSize.width / 2), sliderX = x, sliderWidth = node.width, - thumbWidth = SLIDER_THUMB_WIDTH, + thumbWidth = thumbSize.width, ) - val thumbY = y + (node.height - SLIDER_THUMB_HEIGHT) / 2 + val thumbY = y + (node.height - thumbSize.height) / 2 val stateName = resolveSliderStateName(trackTheme, variant, enabled, hovered, dragging, focused) node.renderState = stateName @@ -235,12 +245,12 @@ fun Slider( drawThemeState(fillState, trackStart, trackY, fillEnd - trackStart, SLIDER_TRACK_HEIGHT) } - val drawThumbWidth = (SLIDER_THUMB_WIDTH * thumbScale).roundToInt() - val drawThumbHeight = (SLIDER_THUMB_HEIGHT * thumbScale).roundToInt() + val drawThumbWidth = (thumbSize.width * thumbScale).roundToInt() + val drawThumbHeight = (thumbSize.height * thumbScale).roundToInt() drawThemeState( thumbState, - thumbX + (SLIDER_THUMB_WIDTH - drawThumbWidth) / 2, - thumbY + (SLIDER_THUMB_HEIGHT - drawThumbHeight) / 2, + thumbX + (thumbSize.width - drawThumbWidth) / 2, + thumbY + (thumbSize.height - drawThumbHeight) / 2, drawThumbWidth, drawThumbHeight, ) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Switch.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Switch.kt index aac96f213..c9084d2d9 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Switch.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Switch.kt @@ -15,10 +15,12 @@ import net.kernelpanicsoft.archie.gui.layout.BoxMeasurePolicy import net.kernelpanicsoft.archie.gui.layout.Layout import net.kernelpanicsoft.archie.gui.layout.Renderer import net.kernelpanicsoft.archie.gui.layout.Alignment +import net.kernelpanicsoft.archie.gui.layout.Size import net.kernelpanicsoft.archie.gui.modifiers.Modifier import net.kernelpanicsoft.archie.gui.modifiers.input.toggleable import net.kernelpanicsoft.archie.gui.modifiers.sizeIn import net.kernelpanicsoft.archie.gui.nodes.UINode +import net.kernelpanicsoft.archie.gui.theme.ComposableTheme import net.kernelpanicsoft.archie.gui.theme.LocalTheme import net.kernelpanicsoft.archie.gui.theme.ThemeVariants import net.kernelpanicsoft.archie.gui.util.extension.drawThemeState @@ -26,15 +28,18 @@ import net.kernelpanicsoft.archie.gui.util.extension.invoke import net.minecraft.client.gui.GuiGraphics import kotlin.time.Duration.Companion.milliseconds +/** Fallback track size when the "switch_track" theme doesn't declare its own [ComposableTheme.minSize]. */ private const val SWITCH_MIN_WIDTH = 34 private const val SWITCH_MIN_HEIGHT = 18 +/** Fallback gap between the thumb and the track's edge, when "switch_track" doesn't declare its own [ComposableTheme.contentPadding]. */ private const val SWITCH_PADDING = 2 +/** Fallback (square) thumb size when the "switch_thumb" theme doesn't declare its own [ComposableTheme.minSize]. */ private const val SWITCH_THUMB_SIZE = 14 -/** Clamps a raw thumb x-offset so the thumb stays within the track, respecting [SWITCH_PADDING]. */ -internal fun resolveSwitchThumbOffset(thumbOffset: Int, trackWidth: Int): Int { - val minOffset = SWITCH_PADDING - val maxOffset = (trackWidth - SWITCH_THUMB_SIZE - SWITCH_PADDING).coerceAtLeast(minOffset) +/** Clamps a raw thumb x-offset so the [thumbWidth]-wide thumb stays within the track, respecting [padding]. */ +internal fun resolveSwitchThumbOffset(thumbOffset: Int, trackWidth: Int, thumbWidth: Int = SWITCH_THUMB_SIZE, padding: Int = SWITCH_PADDING): Int { + val minOffset = padding + val maxOffset = (trackWidth - thumbWidth - padding).coerceAtLeast(minOffset) return thumbOffset.coerceIn(minOffset, maxOffset) } @@ -93,9 +98,19 @@ fun Switch( val trackTheme = theme.getComposableTheme(trackTexture) val thumbTheme = theme.getComposableTheme(thumbTexture) val measurePolicy = remember { BoxMeasurePolicy(Alignment.CenterStart) } - val sizeModifier = Modifier.sizeIn(minWidth = SWITCH_MIN_WIDTH, minHeight = SWITCH_MIN_HEIGHT) + // A theme's track/thumb art isn't always native to SWITCH_MIN_WIDTH/HEIGHT and + // SWITCH_THUMB_SIZE's proportions (tuned for the default look) - min_size on the + // "switch_track"/"switch_thumb" theme entries lets a theme declare its own real dimensions + // instead of getting force-stretched into ones it wasn't designed for. Likewise, + // content_padding's horizontal component overrides how far the thumb sits from the track's + // edge - some art (e.g. a track drawn flush to its own bounds) wants the thumb sitting + // right in the corner instead of inset by the default gap. + val trackSize = trackTheme.minSize ?: Size(SWITCH_MIN_WIDTH, SWITCH_MIN_HEIGHT) + val thumbSize = thumbTheme.minSize ?: Size(SWITCH_THUMB_SIZE, SWITCH_THUMB_SIZE) + val padding = trackTheme.contentPadding?.horizontal ?: SWITCH_PADDING + val sizeModifier = Modifier.sizeIn(minWidth = trackSize.width, minHeight = trackSize.height) val thumbOffset = animateInt( - targetValue = if (checked) SWITCH_MIN_WIDTH - SWITCH_THUMB_SIZE - SWITCH_PADDING else SWITCH_PADDING, + targetValue = if (checked) trackSize.width - thumbSize.width - padding else padding, spec = AnimationSpec(durationMillis = 140.milliseconds, easing = Easings.OutCubic), ) @@ -132,9 +147,9 @@ fun Switch( drawThemeState(trackState, x, y, node.width, node.height) - val thumbX = x + resolveSwitchThumbOffset(thumbOffset, node.width) - val thumbY = y + ((node.height - SWITCH_THUMB_SIZE) / 2) - drawThemeState(thumbState, thumbX, thumbY, SWITCH_THUMB_SIZE, SWITCH_THUMB_SIZE) + val thumbX = x + resolveSwitchThumbOffset(thumbOffset, node.width, thumbSize.width, padding) + val thumbY = y + ((node.height - thumbSize.height) / 2) + drawThemeState(thumbState, thumbX, thumbY, thumbSize.width, thumbSize.height) } }, ) diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_handle.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_handle.json index 33c070cee..39f8ae809 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_handle.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_handle.json @@ -6,8 +6,8 @@ "width": 9, "height": 9 }, - "width": 8, - "height": 20 + "width": 12, + "height": 16 }, "focused": { "texture": "archie:bedrock/slider_handle_highlighted_dark" @@ -15,5 +15,9 @@ "clicked": { "texture": "archie:bedrock/slider_handle_highlighted_dark" } + }, + "min_size": { + "width": 12, + "height": 16 } } diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_thumb.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_thumb.json index c8e4c6346..9285fd74e 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_thumb.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_thumb.json @@ -3,11 +3,11 @@ "default": { "texture": "archie:bedrock/switch_thumb_dark", "texture_size": { - "width": 14, - "height": 14 + "width": 15, + "height": 15 }, - "width": 14, - "height": 14 + "width": 15, + "height": 15 }, "disabled": { "texture": "archie:bedrock/switch_thumb_disabled_dark" diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json index 7e6a7dffa..e305b837c 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/switch_track.json @@ -3,11 +3,11 @@ "default": { "texture": "archie:bedrock/switch_track_dark", "texture_size": { - "width": 34, - "height": 18 + "width": 28, + "height": 13 }, - "width": 34, - "height": 18 + "width": 28, + "height": 13 }, "focused": { "texture": "archie:bedrock/switch_track_focused_dark" @@ -21,5 +21,12 @@ "disabled": { "texture": "archie:bedrock/switch_track_disabled_dark" } + }, + "min_size": { + "width": 28, + "height": 13 + }, + "content_padding": { + "horizontal": 0 } } diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider_handle.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider_handle.json index 1d04b1ed1..282b3a69a 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider_handle.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider_handle.json @@ -6,8 +6,8 @@ "width": 6, "height": 6 }, - "width": 8, - "height": 20 + "width": 12, + "height": 16 }, "focused": { "texture": "archie:bedrock/slider_handle_highlighted" @@ -15,5 +15,9 @@ "clicked": { "texture": "archie:bedrock/slider_handle_highlighted" } + }, + "min_size": { + "width": 12, + "height": 16 } } diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_thumb.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_thumb.json index d1438deb8..779e43f9b 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_thumb.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_thumb.json @@ -3,11 +3,11 @@ "default": { "texture": "archie:bedrock/switch_thumb", "texture_size": { - "width": 14, - "height": 14 + "width": 15, + "height": 15 }, - "width": 14, - "height": 14 + "width": 15, + "height": 15 }, "disabled": { "texture": "archie:bedrock/switch_thumb_disabled" diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_track.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_track.json index e75968eb5..48970ba0d 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_track.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/switch_track.json @@ -3,11 +3,11 @@ "default": { "texture": "archie:bedrock/switch_track", "texture_size": { - "width": 34, - "height": 18 + "width": 28, + "height": 13 }, - "width": 34, - "height": 18 + "width": 28, + "height": 13 }, "focused": { "texture": "archie:bedrock/switch_track_focused" @@ -21,5 +21,12 @@ "disabled": { "texture": "archie:bedrock/switch_track_disabled" } + }, + "min_size": { + "width": 28, + "height": 13 + }, + "content_padding": { + "horizontal": 0 } } diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_clicked_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_clicked_dark.png.mcmeta index 2375483bc..446928b3c 100644 --- a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_clicked_dark.png.mcmeta +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_clicked_dark.png.mcmeta @@ -4,7 +4,12 @@ "type": "nine_slice", "width": 9, "height": 9, - "border": 2 + "border": { + "left": 1, + "top": 1, + "right": 1, + "bottom": 1 + } } } } diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_dark.png.mcmeta index 2375483bc..f70aadcc2 100644 --- a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_dark.png.mcmeta +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_dark.png.mcmeta @@ -4,7 +4,12 @@ "type": "nine_slice", "width": 9, "height": 9, - "border": 2 + "border": { + "left": 1, + "top": 1, + "right": 1, + "bottom": 3 + } } } } diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted_dark.png.mcmeta b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted_dark.png.mcmeta index 2375483bc..f70aadcc2 100644 --- a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted_dark.png.mcmeta +++ b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/button_highlighted_dark.png.mcmeta @@ -4,7 +4,12 @@ "type": "nine_slice", "width": 9, "height": 9, - "border": 2 + "border": { + "left": 1, + "top": 1, + "right": 1, + "bottom": 3 + } } } } diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb.png index 32e846873db2e29a5a0439816aae7e2d0875d24f..1d56e42c0a74f0539407d604627bf5ac868e4737 100644 GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^{2}r4cY)K0f>o8NBw8n*b@vQcqG%+?dkFG{no0GGO ki;GTfnyHj-95iV)gM=5CSy-S(9?)gi_-DziWGri=b%+1WK5|sGSdD5{v lb7Cgts@z`L^F*zP;nX26lY?*Dg@A@Lc)I$ztaD0e0suNlEDHbt diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_thumb_dark.png index c471d07d43de5680eb4ae53bbee8da91f893301c..858731f4bbe40c7f4adbeabba84a3f168d5cc31d 100644 GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^{2BzLSjNf z0tXL|LgeFLU!@ghNlHja+}l&>yv0pWP>>A-)F!QUVOl(^y(g_gsnONu%X jQ>=eqUhbcg$;NQ@H_v^89V*U1!x=nX{an^LB{Ts5F%~QH literal 137 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0y_08bakkcv6UBBG*y&pWUsBqk&z zY}mL_(ev^|Wp@QBNeKxFyShIfQ`G9}>exU)ZPFSiro~d33kBu+H~diae7w|qy2rMO hM$7Aded$c;Whimtx%)d_dj`;K22WQ%mvv4FO#putDq{cu diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track.png index ecf0c88b5d986c72b1dee2d15bafc6795488f157..7d20d61a478ea185ffe5b8febb3c97d44175ef43 100644 GIT binary patch literal 203 zcmeAS@N?(olHy`uVBq!ia0vp^G9Wew8<5=9&bJCkP4aYc45^rtJZ0j}(Z%KYxFJ-!tg^$&)96Di0iRnPobK zd5+jTjS7!ViF4-6Dd^$e@L-DKVWTGrZqE-Nc)%GH#kh*~B~Vk~BffC0W0?z&vQ%!; zQDtFM^Kf<92{d(|(At-p%*^+e^z$2B+O5ON5HnNpd5U`JKcMRvJYD@<);T3K0RW_F BQwRV6 literal 224 zcmV<603ZK}P)aH=I2JugITklyMry}0AR&)s%F?0;IYsioo1$3x{<57Yp|1BM z4u}B9Hkbu&%}7;kicAc{gH@5jg2c3Vc6Jd}HJ=p`06!IRV}(Z%KYxFJ-!tg^$&)96Di0iRnPobK zd5+jTjS7!ViF4-6Dd^$e@L-DKVWTGrZqE-Nc)%GH#kh*~B~Vk~BffC0W0?z&vQ%!; zQDtFM^Kf<92{d(|(At-p%*^+e^z$2B+O5ON5HnNpd5U`JKcMRvJYD@<);T3K0RW_F BQwRV6 literal 224 zcmV<603ZK}P)aH=I2JugITklyMry}0AR&)s%F?0;IYsioo1$3x{<57Yp|1BM z4u}B9Hkbu&%}7;kicAc{gH@5jg2c3Vc6Jd}HJ=p`06!IRVFS$s(enf6p6mFmN+7 zGdHJp*xh84i7%em*x%o;{^YgO=gyrwaNvMJ zg;UA}ix$@TDYb6eK<&)Vk9)E<@wBsA^E~Kw+QYYyWy9vp$q_xiJZx;fS9+{iWH0FS z{`vEBa#iQlJ9nIVj=o5^;Bt0BAqU8AW_x>n*R}=AS&I%V;1oP|@R9)&gX&^cofV3Q ReSywp@O1TaS?83{1OQv;Ppbd` diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_disabled_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_disabled_dark.png index d0ecb86d33923f53ccf60e65747c8576edcaace2..d5ccd419e43a55b1b8ce9f89d5513c5a97e492cd 100644 GIT binary patch literal 196 zcmeAS@N?(olHy`uVBq!ia0vp^G9Wew8<5=9&bJCkb$hxvhE&W+77-Qwd)~qQz@Y;N z4kSn}@}IQqakie(hmRj8=GFS$s(enf6p6mFmN+7 zGdHJp*xh84i7%em*x%o;{^YgO=gyrwaNvMJ zg;UA}ix$@TDYb6eK<&)Vk9)E<@wBsA^E~Kw+QYYyWy9vp$q_xiJZx;fS9+{iWH0FS z{`vEBa#iQlJ9nIVj=o5^;Bt0BAqU8AW_x>n*R}=AS&I%V;1oP|@R9)&gX&^cofV3Q ReSywp@O1TaS?83{1OQv;Ppbd` diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_focused.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_focused.png index 9d97103840f9d322af5823e036f4e0b374a59fcd..1d4b4588828106ccedfbbf2a8085cfe001f0efa3 100644 GIT binary patch literal 199 zcmeAS@N?(olHy`uVBq!ia0vp^G9Wew8<5=9&bJCk^?ABDhE&W+4haeQb>6}Kz@Y;N z4kYYo>s0Z5?)=ZNqq}=z&dt-)*DHt_85kJU>~Np;Y<>LxNlWI8d#U}NKR8^H@1>zWAmSIi7PE}`-rL56gTe~DWM4fRz*!` literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^NFS$sr*jzs?(QFmN+7 zGdDk!QrpC9A^)l0F(#%*$$d}F&#uNA@c_vU2PEX*P0T-WeSQ4NYp2hiJ$vB50fP#s zlnWLutn;7Tn{b;41kPoLoHUXVdt=a`&-Kx&Md-lUv%QIiW`+_HCV|GU1WFt>n|%8E zI{cMtSWz*zq1fVPM`6?EV~RQ;y+V`r0Ll3$%~))8Cor@5b{OzYYZGK}w^w;t(Nb;! PbTEUbtDnm{r-UW|y{Jrp diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_focused_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_focused_dark.png index 9d97103840f9d322af5823e036f4e0b374a59fcd..1d4b4588828106ccedfbbf2a8085cfe001f0efa3 100644 GIT binary patch literal 199 zcmeAS@N?(olHy`uVBq!ia0vp^G9Wew8<5=9&bJCk^?ABDhE&W+4haeQb>6}Kz@Y;N z4kYYo>s0Z5?)=ZNqq}=z&dt-)*DHt_85kJU>~Np;Y<>LxNlWI8d#U}NKR8^H@1>zWAmSIi7PE}`-rL56gTe~DWM4fRz*!` literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^NFS$sr*jzs?(QFmN+7 zGdDk!QrpC9A^)l0F(#%*$$d}F&#uNA@c_vU2PEX*P0T-WeSQ4NYp2hiJ$vB50fP#s zlnWLutn;7Tn{b;41kPoLoHUXVdt=a`&-Kx&Md-lUv%QIiW`+_HCV|GU1WFt>n|%8E zI{cMtSWz*zq1fVPM`6?EV~RQ;y+V`r0Ll3$%~))8Cor@5b{OzYYZGK}w^w;t(Nb;! PbTEUbtDnm{r-UW|y{Jrp diff --git a/test/common/src/main/kotlin/net/kernelpanicsoft/archie/test/TestScreen.kt b/test/common/src/main/kotlin/net/kernelpanicsoft/archie/test/TestScreen.kt index 004b1089b..fdf1122f8 100644 --- a/test/common/src/main/kotlin/net/kernelpanicsoft/archie/test/TestScreen.kt +++ b/test/common/src/main/kotlin/net/kernelpanicsoft/archie/test/TestScreen.kt @@ -66,7 +66,7 @@ class TestScreen(menu: TestMenu, playerInventory: Inventory, title: Component) : val layerManager = LocalLayerManager.current var syncedValue by observeProperty("test", "") val test = syncedValue ?: "" - Theme(type = "bedrock") { + Theme(type = "bedrock", mode = "dark") { Box(modifier = Modifier.width(contentWidth + 16)) { TabContainerPanel(contentWidth) { for (showcase in TestKind.entries) { From 1c3d2adc384de03490bda662c37a79e4659bc5af Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Wed, 12 Aug 2026 22:13:00 -0400 Subject: [PATCH 26/30] Draw the slider fill at full track thickness for bedrock, matching real Bedrock UI Measured pixel-for-pixel against a real Bedrock settings screenshot: the fill isn't a thin accent line over a much taller track like Java's own vanilla-style slider - it's a full-thickness bicolor bar (filled portion one shade, unfilled another), both the exact same thickness as the track. Slider.kt's SLIDER_TRACK_HEIGHT=2 fill height (shared by every theme) was producing a thin sliver instead. Slider.kt now reads "slider_fill"'s own min_size.height, falling back to SLIDER_TRACK_HEIGHT so Java's look is unchanged. Bedrock declares 14 (close to the slider_handle thumb's own 16, leaving it slightly overhanging the fill/track exactly as in the reference screenshot). Verified via full compile and the live runGametestClient suite, 23/23. Co-Authored-By: Claude Sonnet 5 --- .../archie/gui/composables/input/Slider.kt | 9 +++++++-- .../archie/archie_themes/bedrock/dark/slider_fill.json | 6 +++++- .../assets/archie/archie_themes/bedrock/slider_fill.json | 6 +++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt index c88b21857..a51d1a670 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt @@ -193,6 +193,11 @@ fun Slider( val sliderSize = trackTheme.minSize ?: Size(SLIDER_MIN_WIDTH, SLIDER_MIN_HEIGHT) val thumbSize = thumbTheme.minSize ?: Size(SLIDER_THUMB_WIDTH, SLIDER_THUMB_HEIGHT) val sizeModifier = Modifier.sizeIn(minWidth = sliderSize.width, minHeight = sliderSize.height) + // Java's fill is a thin accent line (SLIDER_TRACK_HEIGHT) drawn over a much taller track, + // but some themes draw the fill as a full-thickness bicolor bar (filled portion one shade, + // unfilled another, both the same thickness as the track itself) - "slider_fill"'s own + // min_size height overrides how tall the fill renders when a theme wants that look. + val fillHeight = fillTheme.minSize?.height ?: SLIDER_TRACK_HEIGHT SliderCore( value = value, onValueChange = onValueChange, @@ -221,7 +226,7 @@ fun Slider( mouseY: Int, partialTick: Float, ) = guiGraphics { - val trackY = y + (node.height - SLIDER_TRACK_HEIGHT) / 2 + val trackY = y + (node.height - fillHeight) / 2 val trackStart = x + (thumbSize.width / 2) val trackEnd = x + node.width - (thumbSize.width / 2) val availableTrack = (trackEnd - trackStart).coerceAtLeast(1) @@ -242,7 +247,7 @@ fun Slider( drawThemeState(trackState, x, y, node.width, node.height) if (fillEnd > trackStart) { - drawThemeState(fillState, trackStart, trackY, fillEnd - trackStart, SLIDER_TRACK_HEIGHT) + drawThemeState(fillState, trackStart, trackY, fillEnd - trackStart, fillHeight) } val drawThumbWidth = (thumbSize.width * thumbScale).roundToInt() diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_fill.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_fill.json index 8aa9b107e..1a961b60a 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_fill.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider_fill.json @@ -7,10 +7,14 @@ "height": 4 }, "width": 8, - "height": 2 + "height": 14 }, "disabled": { "texture": "archie:bedrock/slider_fill_disabled_dark" } + }, + "min_size": { + "width": 8, + "height": 14 } } diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider_fill.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider_fill.json index 76eef1517..410f7fe5e 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider_fill.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider_fill.json @@ -7,10 +7,14 @@ "height": 3 }, "width": 8, - "height": 2 + "height": 14 }, "disabled": { "texture": "archie:bedrock/slider_fill_disabled" } + }, + "min_size": { + "width": 8, + "height": 14 } } From 537658f0bc4f05ba422d1ea7d1c9e3bd2cabeb9c Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Wed, 12 Aug 2026 22:23:29 -0400 Subject: [PATCH 27/30] Tighten bedrock slider track height and pull in switch_track pixel refinement The slider track was still rendering at the full SLIDER_MIN_HEIGHT=20 container height, leaving a too-wide 3px gap around the 14px-tall fill. Gives "slider" its own min_size (96x16, 1px margin on each side of the fill) instead of falling back to the shared default. Also pulls in the user's small switch_track.png refinement (a 1px boundary shift between the green/gray halves). Verified via full compile and the live runGametestClient suite, 23/23. Co-Authored-By: Claude Sonnet 5 --- .../archie_themes/bedrock/dark/slider.json | 8 ++++++-- .../archie/archie_themes/bedrock/slider.json | 8 ++++++-- .../gui/sprites/bedrock/switch_track.png | Bin 203 -> 196 bytes .../gui/sprites/bedrock/switch_track_dark.png | Bin 203 -> 196 bytes .../sprites/bedrock/switch_track_disabled.png | Bin 196 -> 192 bytes .../bedrock/switch_track_disabled_dark.png | Bin 196 -> 192 bytes .../sprites/bedrock/switch_track_focused.png | Bin 199 -> 196 bytes .../bedrock/switch_track_focused_dark.png | Bin 199 -> 196 bytes 8 files changed, 12 insertions(+), 4 deletions(-) diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider.json index 37eaad07c..e4617b206 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/dark/slider.json @@ -6,8 +6,8 @@ "width": 5, "height": 5 }, - "width": 200, - "height": 20 + "width": 96, + "height": 16 }, "focused": { "texture": "archie:bedrock/slider_highlighted_dark" @@ -15,5 +15,9 @@ "clicked": { "texture": "archie:bedrock/slider_highlighted_dark" } + }, + "min_size": { + "width": 96, + "height": 16 } } diff --git a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider.json b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider.json index b1c96aff6..20564d670 100644 --- a/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider.json +++ b/core/common/src/main/resources/assets/archie/archie_themes/bedrock/slider.json @@ -6,8 +6,8 @@ "width": 3, "height": 3 }, - "width": 200, - "height": 20 + "width": 96, + "height": 16 }, "focused": { "texture": "archie:bedrock/slider_highlighted" @@ -15,5 +15,9 @@ "clicked": { "texture": "archie:bedrock/slider_highlighted" } + }, + "min_size": { + "width": 96, + "height": 16 } } diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track.png index 7d20d61a478ea185ffe5b8febb3c97d44175ef43..3be5176f3fe01a7a38db073acd382e7536c7091e 100644 GIT binary patch delta 168 zcmX@jc!Y6+N`1Gdi(^Q|oa8AJC;mO};C|rHfddB?+&u5G=~%dKlgjJL-``}9II*#@ zwfXydT>kU-clo3%#YII%1|TpojrS?T@>X>&xk)(^>gwt;kC}8VQcrB)v7B-7r-4C5 z)0D7=EXQ9!O;dIpp4wFym0{>OPsf|Hv5_-KFfb2j=z2w!%s-9lFCR1ae9`k}bhS|U U$SeDklK}`kUHx3vIVCg!0E4PW&j0`b delta 175 zcmV;g08szL0m}i9B!7}gL_t(|oMV)ck^Rp=N5I0sz`&r{&q$J{>xKUrgoTAjw(RZO zcMuC17#J8nJt0l=_wV0HQqRfB$-uzCz`#VBHIxIK7M>`0NU~v^oSbls3$WmnA;U7{ zU`FS|z~Bi|S^=sB>G$v7QQ{6)D#mG{#RPe>EF}a6CP+m@xhNSJ7`_s%=_vyP-lUHY dFw(m$1pr)`A?IW#b^ZVV002ovPDHLkV1gNmQ0D*u diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_dark.png index 7d20d61a478ea185ffe5b8febb3c97d44175ef43..3be5176f3fe01a7a38db073acd382e7536c7091e 100644 GIT binary patch delta 168 zcmX@jc!Y6+N`1Gdi(^Q|oa8AJC;mO};C|rHfddB?+&u5G=~%dKlgjJL-``}9II*#@ zwfXydT>kU-clo3%#YII%1|TpojrS?T@>X>&xk)(^>gwt;kC}8VQcrB)v7B-7r-4C5 z)0D7=EXQ9!O;dIpp4wFym0{>OPsf|Hv5_-KFfb2j=z2w!%s-9lFCR1ae9`k}bhS|U U$SeDklK}`kUHx3vIVCg!0E4PW&j0`b delta 175 zcmV;g08szL0m}i9B!7}gL_t(|oMV)ck^Rp=N5I0sz`&r{&q$J{>xKUrgoTAjw(RZO zcMuC17#J8nJt0l=_wV0HQqRfB$-uzCz`#VBHIxIK7M>`0NU~v^oSbls3$WmnA;U7{ zU`FS|z~Bi|S^=sB>G$v7QQ{6)D#mG{#RPe>EF}a6CP+m@xhNSJ7`_s%=_vyP-lUHY dFw(m$1pr)`A?IW#b^ZVV002ovPDHLkV1gNmQ0D*u diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_disabled.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_disabled.png index d5ccd419e43a55b1b8ce9f89d5513c5a97e492cd..abdeda60e84f8ca227dff7cf8b715f593627979e 100644 GIT binary patch delta 164 zcmV;V09*gW0l)!}B!7oVL_t(|oMRLf75&dZN5I0sz`$S`qEE8MtGn)#tmo6GPY??k z7#J8nz57Oz=AS=*lBAxUot=S!fq{XEBwbVhI4!KtXeHS&c6N3+#sye#%8+3haxkOw zVPNnCDXjq2g7oLlpD1yMD;49kuyIB;S(Xw40~4hD{5&LU#2uIP@c~A9m!$xD4Is>flp(`1 z>flp(`1 z* z3=9k(?vbYX>({R&spsbAW?*1oU?AOV6at(U?hUz3vSHlZ+;EHwu;7#-!!qPxM(4x8 z;0aP%0jdS**RNku;tp3T#%ZDR8db6^B?Ja0NF^kB85kHolOd$>8=j<(4=~cZECm2p W10fgR9t}$X0000MGnY}w0~ zuOJpOFfcHDxJR1iuV25Cq@J6bn}LCWfq^uyQ32qzaBs+Mk`3eL=7wWjfCZ-v8I~aj zGddpz22YUE3Q#RbzkdCS5_h;#F-{Ae*Qkw+8z)0`1 Z6adwrAuL~=JO%&&002ovPDHLkV1mkPOFjSq diff --git a/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_focused_dark.png b/core/common/src/main/resources/assets/archie/textures/gui/sprites/bedrock/switch_track_focused_dark.png index 1d4b4588828106ccedfbbf2a8085cfe001f0efa3..f38bf7947010f6768bef78badd52e3e777b0f430 100644 GIT binary patch delta 168 zcmV;Z09XIV0mK22B!7!ZL_t(|oMTi}RQk_AN5I0sz`$U*hKXd2&$2~G*7Ne^D~N>* z3=9k(?vbYX>({R&spsbAW?*1oU?AOV6at(U?hUz3vSHlZ+;EHwu;7#-!!qPxM(4x8 z;0aP%0jdS**RNku;tp3T#%ZDR8db6^B?Ja0NF^kB85kHolOd$>8=j<(4=~cZECm2p W10fgR9t}$X0000MGnY}w0~ zuOJpOFfcHDxJR1iuV25Cq@J6bn}LCWfq^uyQ32qzaBs+Mk`3eL=7wWjfCZ-v8I~aj zGddpz22YUE3Q#RbzkdCS5_h;#F-{Ae*Qkw+8z)0`1 Z6adwrAuL~=JO%&&002ovPDHLkV1mkPOFjSq From 27498318c2d64f02a0fc1e23a501443267500619 Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Wed, 12 Aug 2026 22:32:57 -0400 Subject: [PATCH 28/30] Fix slider fill leaving a permanent gap at both edges, never reaching 0%/100% trackStart/trackEnd inset by half the thumb's width on both ends, meant to keep the thumb's center within the track - but resolveSliderThumbX already independently clamps the thumb's own position into [x, x+width-thumbWidth], so insetting the fill's bounds too doubled up the margin, leaving a gap the fill could never close even at 100% (visibly: "the fill doesn't touch the left edge"). trackStart/trackEnd now span the track's real full width; the thumb still can't run past the track edges since its own clamp is unaffected by this change. Verified via full compile and the live runGametestClient suite, 23/23. Co-Authored-By: Claude Sonnet 5 --- .../archie/gui/composables/input/Slider.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt index a51d1a670..eaaafc221 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/composables/input/Slider.kt @@ -227,8 +227,13 @@ fun Slider( partialTick: Float, ) = guiGraphics { val trackY = y + (node.height - fillHeight) / 2 - val trackStart = x + (thumbSize.width / 2) - val trackEnd = x + node.width - (thumbSize.width / 2) + // Spans the track's actual full width (0%..100% -> fully unfilled..fully + // filled, touching both edges) - the thumb's own half-width inset is handled + // separately by resolveSliderThumbX's clamp below, so insetting trackStart/ + // trackEnd here too would double up, leaving a permanent gap the fill could + // never close even at 100%. + val trackStart = x + val trackEnd = x + node.width val availableTrack = (trackEnd - trackStart).coerceAtLeast(1) val fillEnd = trackStart + (availableTrack * normalizedValue).roundToInt() val thumbX = resolveSliderThumbX( From aabf09990afe9b7b4162dcbe82652bb2f24593e6 Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Wed, 12 Aug 2026 23:05:22 -0400 Subject: [PATCH 29/30] Route NBT/serializer lookups through SerializationManager instead of module-unaware globals NBTHolderImpl and its item/fluid-stack counterparts, plus every ArchieItemStorage/ArchieFluidStorage/ArchieEnergyStorage/ArchieItemSlot/ ArchieFluidSlot snapshot, all used the standalone NBT.kt global (a bare Nbt {} instance with no serializersModule) instead of SerializationManager.nbt (the one actually kept in sync with SerializationManager's shared module - registered contextual serializers, anything a mod registers via SerializationManager.overwriteWith, etc.). Swapped every one of those 8 files over to SerializationManager.nbt. Same root problem, different shape, in a few serializer() lookups: - BlockEntityStateComposables.observeProperty/ItemStateComposables. observeItemProperty called the bare top-level serializer(), which resolves against EmptySerializersModule and ignores the shared module entirely. - FieldType's EnumSelector/Selector (Cloth Config enum/selector fields) and RegistryFriendlyByteBuf.write() called KClass.serializer()/ data::class.serializer(), kotlinx.serialization's raw-reflection lookup - same problem, different API shape. Exposed SerializationManager.module (the shared SerializersModule) so this class of lookup can be done properly: module.serializer() for the two reified call sites, module.serializer(kClass.createType()) for the two non-reified ones. RegistryFriendlyByteBuf.write() itself is now inline reified, matching a companion .read() added alongside it, so both go through the same module.serializer() path without reflection. Verified via full compile and the live runGametestClient suite, 23/23. Co-Authored-By: Claude Sonnet 5 --- .../archie/config/FieldType.kt | 13 +++++---- .../BlockEntityStateComposables.kt | 3 +- .../archie/gui/item/ItemStateComposables.kt | 3 +- .../serialization/FluidStackNBTHolderImpl.kt | 26 ++++++++--------- .../serialization/ItemStackNBTHolderImpl.kt | 26 ++++++++--------- .../archie/serialization/NBTHolderImpl.kt | 26 ++++++++--------- .../serialization/SerializationManager.kt | 9 ++++++ .../archie/serialization/Utils.kt | 10 +++++-- .../archie/transfer/ArchieEnergyStorage.kt | 6 ++-- .../archie/transfer/ArchieFluidSlot.kt | 6 ++-- .../archie/transfer/ArchieFluidStorage.kt | 6 ++-- .../archie/transfer/ArchieItemSlot.kt | 6 ++-- .../archie/transfer/ArchieItemStorage.kt | 6 ++-- .../kernelpanicsoft/archie/test/TestScreen.kt | 29 ------------------- 14 files changed, 82 insertions(+), 93 deletions(-) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/config/FieldType.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/config/FieldType.kt index 4075d0014..2486e5490 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/config/FieldType.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/config/FieldType.kt @@ -1,6 +1,5 @@ package net.kernelpanicsoft.archie.config -import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.MapSerializer @@ -8,10 +7,12 @@ import kotlinx.serialization.builtins.serializer import kotlinx.serialization.serializer import net.kernelpanicsoft.archie.serialization.DeferredListSerializer import net.kernelpanicsoft.archie.serialization.DeferredMapSerializer +import net.kernelpanicsoft.archie.serialization.SerializationManager import net.kernelpanicsoft.archie.serialization.serializers.ColorSerializer import net.kernelpanicsoft.archie.serialization.serializers.ResourceLocationSerializer import net.minecraft.resources.ResourceLocation import kotlin.reflect.KClass +import kotlin.reflect.full.createType /** * Tags a [DataSpec] field with its runtime type and the [KSerializer] used to read/write it, @@ -81,14 +82,16 @@ internal sealed class FieldType data class EnumSelector>(val kClass: KClass) : FieldType() { - @OptIn(InternalSerializationApi::class) - override val serializer: KSerializer = kClass.serializer() + // Module-aware, unlike the bare KClass.serializer() reflective lookup - matters if T + // (or a field of it) ever needs a contextual serializer registered via SerializationManager. + @Suppress("UNCHECKED_CAST") + override val serializer: KSerializer = SerializationManager.module.serializer(kClass.createType()) as KSerializer } data class Selector(val kClass: KClass) : FieldType() { - @OptIn(InternalSerializationApi::class) - override val serializer: KSerializer = kClass.serializer() + @Suppress("UNCHECKED_CAST") + override val serializer: KSerializer = SerializationManager.module.serializer(kClass.createType()) as KSerializer } data object IntList : FieldType>() diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/blockentity/BlockEntityStateComposables.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/blockentity/BlockEntityStateComposables.kt index c84610858..3d0f5f803 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/blockentity/BlockEntityStateComposables.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/blockentity/BlockEntityStateComposables.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.compositionLocalOf import kotlinx.serialization.serializer +import net.kernelpanicsoft.archie.serialization.SerializationManager /** * Provides the current block entity state to composables in the composition tree. @@ -40,5 +41,5 @@ inline fun observeProperty( initialValue: T? = null, ): MutableState { val state = LocalBlockEntityState.current ?: throw RuntimeException("No block entity state available in composition") - return state.observeProperty(propertyName, serializer(), initialValue) + return state.observeProperty(propertyName, SerializationManager.module.serializer(), initialValue) } diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/item/ItemStateComposables.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/item/ItemStateComposables.kt index f35ea05ef..bf8989aad 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/item/ItemStateComposables.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/item/ItemStateComposables.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.compositionLocalOf import kotlinx.serialization.serializer +import net.kernelpanicsoft.archie.serialization.SerializationManager /** * Provides the current [ComposeItemContainerMenu]'s state to composables in the composition @@ -45,5 +46,5 @@ inline fun observeItemProperty( initialValue: T? = null, ): MutableState { val state = LocalItemState.current ?: throw RuntimeException("No item container state available in composition") - return state.observeProperty(propertyName, serializer(), initialValue) + return state.observeProperty(propertyName, SerializationManager.module.serializer(), initialValue) } diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/FluidStackNBTHolderImpl.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/FluidStackNBTHolderImpl.kt index 39cb03450..8717fa351 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/FluidStackNBTHolderImpl.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/FluidStackNBTHolderImpl.kt @@ -49,19 +49,19 @@ class FluidStackNBTHolderImpl(private val stack: FluidStack) : NBTHolder { loadFromStack() return runCatching { - NBT.decodeFromNbtTagRootless(serializer, data.getOrPut(property.name.toSnakeCase()) { - NBT.encodeToNbtTagRootless(serializer, default()) + SerializationManager.nbt.decodeFromNbtTagRootless(serializer, data.getOrPut(property.name.toSnakeCase()) { + SerializationManager.nbt.encodeToNbtTagRootless(serializer, default()) }) }.recover { val ret = default() - data[property.name.toSnakeCase()] = NBT.encodeToNbtTagRootless(serializer, ret) + data[property.name.toSnakeCase()] = SerializationManager.nbt.encodeToNbtTagRootless(serializer, ret) ret }.getOrThrow() } override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { - data[property.name.toSnakeCase()] = NBT.encodeToNbtTagRootless(serializer, value) + data[property.name.toSnakeCase()] = SerializationManager.nbt.encodeToNbtTagRootless(serializer, value) saveToStack() } } @@ -84,19 +84,19 @@ class FluidStackNBTHolderImpl(private val stack: FluidStack) : NBTHolder { loadFromStack() return ObservableList(runCatching { - NBT.decodeFromNbtTagRootless(ListSerializer(serializer), data.getOrPut(property.name.toSnakeCase()) { - NBT.encodeToNbtTagRootless(ListSerializer(serializer), default()) + SerializationManager.nbt.decodeFromNbtTagRootless(ListSerializer(serializer), data.getOrPut(property.name.toSnakeCase()) { + SerializationManager.nbt.encodeToNbtTagRootless(ListSerializer(serializer), default()) }) }.recover { val ret = default() - data[property.name.toSnakeCase()] = NBT.encodeToNbtTagRootless(ListSerializer(serializer), ret) + data[property.name.toSnakeCase()] = SerializationManager.nbt.encodeToNbtTagRootless(ListSerializer(serializer), ret) ret }.getOrThrow().toMutableList()) { list -> setValue(thisRef, property, list)} } override fun setValue(thisRef: Any?, property: KProperty<*>, value: MutableList) { - data[property.name.toSnakeCase()] = NBT.encodeToNbtTagRootless(ListSerializer(serializer), value) + data[property.name.toSnakeCase()] = SerializationManager.nbt.encodeToNbtTagRootless(ListSerializer(serializer), value) saveToStack() } } @@ -119,19 +119,19 @@ class FluidStackNBTHolderImpl(private val stack: FluidStack) : NBTHolder { loadFromStack() return ObservableMap(runCatching { - NBT.decodeFromNbtTagRootless(MapSerializer(String.serializer(), serializer), data.getOrPut(property.name.toSnakeCase()) { - NBT.encodeToNbtTagRootless(MapSerializer(String.serializer(), serializer), default()) + SerializationManager.nbt.decodeFromNbtTagRootless(MapSerializer(String.serializer(), serializer), data.getOrPut(property.name.toSnakeCase()) { + SerializationManager.nbt.encodeToNbtTagRootless(MapSerializer(String.serializer(), serializer), default()) }) }.recover { val ret = default() - data[property.name.toSnakeCase()] = NBT.encodeToNbtTagRootless(MapSerializer(String.serializer(), serializer), ret) + data[property.name.toSnakeCase()] = SerializationManager.nbt.encodeToNbtTagRootless(MapSerializer(String.serializer(), serializer), ret) ret }.getOrThrow().toMutableMap()) { map -> setValue(thisRef, property, map)} } override fun setValue(thisRef: Any?, property: KProperty<*>, value: MutableMap) { - data[property.name.toSnakeCase()] = NBT.encodeToNbtTagRootless(MapSerializer(String.serializer(), serializer), value) + data[property.name.toSnakeCase()] = SerializationManager.nbt.encodeToNbtTagRootless(MapSerializer(String.serializer(), serializer), value) saveToStack() } } @@ -239,6 +239,6 @@ class FluidStackNBTHolderImpl(private val stack: FluidStack) : NBTHolder override fun updateProperty(propertyName: String, serializer: KSerializer, value: T) { - this.data[propertyName] = NBT.encodeToNbtTagRootless(serializer, value) + this.data[propertyName] = SerializationManager.nbt.encodeToNbtTagRootless(serializer, value) } } \ No newline at end of file diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/ItemStackNBTHolderImpl.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/ItemStackNBTHolderImpl.kt index e97bfe7b7..3b93ecb25 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/ItemStackNBTHolderImpl.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/ItemStackNBTHolderImpl.kt @@ -61,19 +61,19 @@ class ItemStackNBTHolderImpl(private val stack: ItemStack) : NBTHolder { loadFromStack() return runCatching { - NBT.decodeFromNbtTagRootless(serializer, data.getOrPut(property.name.toSnakeCase()) { - NBT.encodeToNbtTagRootless(serializer, default()) + SerializationManager.nbt.decodeFromNbtTagRootless(serializer, data.getOrPut(property.name.toSnakeCase()) { + SerializationManager.nbt.encodeToNbtTagRootless(serializer, default()) }) }.recover { val ret = default() - data[property.name.toSnakeCase()] = NBT.encodeToNbtTagRootless(serializer, ret) + data[property.name.toSnakeCase()] = SerializationManager.nbt.encodeToNbtTagRootless(serializer, ret) ret }.getOrThrow() } override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { - data[property.name.toSnakeCase()] = NBT.encodeToNbtTagRootless(serializer, value) + data[property.name.toSnakeCase()] = SerializationManager.nbt.encodeToNbtTagRootless(serializer, value) if (thisRef is SyncedItemHolder && property.name.toSnakeCase() in sync) thisRef.onSyncedPropertyChanged(property.name.toSnakeCase(), serializer, value) saveToStack() @@ -109,19 +109,19 @@ class ItemStackNBTHolderImpl(private val stack: ItemStack) : NBTHolder { loadFromStack() return ObservableList(runCatching { - NBT.decodeFromNbtTagRootless(ListSerializer(serializer), data.getOrPut(property.name.toSnakeCase()) { - NBT.encodeToNbtTagRootless(ListSerializer(serializer), default()) + SerializationManager.nbt.decodeFromNbtTagRootless(ListSerializer(serializer), data.getOrPut(property.name.toSnakeCase()) { + SerializationManager.nbt.encodeToNbtTagRootless(ListSerializer(serializer), default()) }) }.recover { val ret = default() - data[property.name.toSnakeCase()] = NBT.encodeToNbtTagRootless(ListSerializer(serializer), ret) + data[property.name.toSnakeCase()] = SerializationManager.nbt.encodeToNbtTagRootless(ListSerializer(serializer), ret) ret }.getOrThrow().toMutableList()) { list -> setValue(thisRef, property, list) } } override fun setValue(thisRef: Any?, property: KProperty<*>, value: MutableList) { - data[property.name.toSnakeCase()] = NBT.encodeToNbtTagRootless(ListSerializer(serializer), value) + data[property.name.toSnakeCase()] = SerializationManager.nbt.encodeToNbtTagRootless(ListSerializer(serializer), value) if (thisRef is SyncedItemHolder && property.name.toSnakeCase() in sync) thisRef.onSyncedPropertyChanged(property.name.toSnakeCase(), ListSerializer(serializer), value.toList()) saveToStack() @@ -156,19 +156,19 @@ class ItemStackNBTHolderImpl(private val stack: ItemStack) : NBTHolder { loadFromStack() return ObservableMap(runCatching { - NBT.decodeFromNbtTagRootless(MapSerializer(String.serializer(), serializer), data.getOrPut(property.name.toSnakeCase()) { - NBT.encodeToNbtTagRootless(MapSerializer(String.serializer(), serializer), default()) + SerializationManager.nbt.decodeFromNbtTagRootless(MapSerializer(String.serializer(), serializer), data.getOrPut(property.name.toSnakeCase()) { + SerializationManager.nbt.encodeToNbtTagRootless(MapSerializer(String.serializer(), serializer), default()) }) }.recover { val ret = default() - data[property.name.toSnakeCase()] = NBT.encodeToNbtTagRootless(MapSerializer(String.serializer(), serializer), ret) + data[property.name.toSnakeCase()] = SerializationManager.nbt.encodeToNbtTagRootless(MapSerializer(String.serializer(), serializer), ret) ret }.getOrThrow().toMutableMap()) { map -> setValue(thisRef, property, map) } } override fun setValue(thisRef: Any?, property: KProperty<*>, value: MutableMap) { - data[property.name.toSnakeCase()] = NBT.encodeToNbtTagRootless(MapSerializer(String.serializer(), serializer), value) + data[property.name.toSnakeCase()] = SerializationManager.nbt.encodeToNbtTagRootless(MapSerializer(String.serializer(), serializer), value) if (thisRef is SyncedItemHolder && property.name.toSnakeCase() in sync) thisRef.onSyncedPropertyChanged(property.name.toSnakeCase(), MapSerializer(String.serializer(), serializer), value.toMap()) saveToStack() @@ -339,7 +339,7 @@ class ItemStackNBTHolderImpl(private val stack: ItemStack) : NBTHolder } else { - this.data[propertyName] = NBT.encodeToNbtTagRootless(serializer, value) + this.data[propertyName] = SerializationManager.nbt.encodeToNbtTagRootless(serializer, value) } // Unlike NBTHolderImpl's `data` (a BlockEntity's own persisted state), `data` here is only // a transient copy - must be flushed to the stack explicitly or a remote edit is lost. diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/NBTHolderImpl.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/NBTHolderImpl.kt index 5c080277c..f4b730b23 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/NBTHolderImpl.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/NBTHolderImpl.kt @@ -53,19 +53,19 @@ class NBTHolderImpl : NBTHolder override fun getValue(thisRef: Any?, property: KProperty<*>): T { return runCatching { - NBT.decodeFromNbtTagRootless(serializer, data.getOrPut(property.name.toSnakeCase()) { - NBT.encodeToNbtTagRootless(serializer, default()) + SerializationManager.nbt.decodeFromNbtTagRootless(serializer, data.getOrPut(property.name.toSnakeCase()) { + SerializationManager.nbt.encodeToNbtTagRootless(serializer, default()) }) }.recover { val ret = default() - data[property.name.toSnakeCase()] = NBT.encodeToNbtTagRootless(serializer, ret) + data[property.name.toSnakeCase()] = SerializationManager.nbt.encodeToNbtTagRootless(serializer, ret) ret }.getOrThrow() } override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { - data[property.name.toSnakeCase()] = NBT.encodeToNbtTagRootless(serializer, value) + data[property.name.toSnakeCase()] = SerializationManager.nbt.encodeToNbtTagRootless(serializer, value) if (thisRef is BlockEntity) { if (property.name.toSnakeCase() in sync) @@ -99,19 +99,19 @@ class NBTHolderImpl : NBTHolder override fun getValue(thisRef: Any?, property: KProperty<*>): MutableList { return ObservableList(runCatching { - NBT.decodeFromNbtTagRootless(ListSerializer(serializer), data.getOrPut(property.name.toSnakeCase()) { - NBT.encodeToNbtTagRootless(ListSerializer(serializer), default()) + SerializationManager.nbt.decodeFromNbtTagRootless(ListSerializer(serializer), data.getOrPut(property.name.toSnakeCase()) { + SerializationManager.nbt.encodeToNbtTagRootless(ListSerializer(serializer), default()) }) }.recover { val ret = default() - data[property.name.toSnakeCase()] = NBT.encodeToNbtTagRootless(ListSerializer(serializer), ret) + data[property.name.toSnakeCase()] = SerializationManager.nbt.encodeToNbtTagRootless(ListSerializer(serializer), ret) ret }.getOrThrow().toMutableList()) { list -> setValue(thisRef, property, list) } } override fun setValue(thisRef: Any?, property: KProperty<*>, value: MutableList) { - data[property.name.toSnakeCase()] = NBT.encodeToNbtTagRootless(ListSerializer(serializer), value) + data[property.name.toSnakeCase()] = SerializationManager.nbt.encodeToNbtTagRootless(ListSerializer(serializer), value) if (thisRef is BlockEntity) { if (property.name.toSnakeCase() in sync) @@ -145,19 +145,19 @@ class NBTHolderImpl : NBTHolder override fun getValue(thisRef: Any?, property: KProperty<*>): MutableMap { return ObservableMap(runCatching { - NBT.decodeFromNbtTagRootless(MapSerializer(String.serializer(), serializer), data.getOrPut(property.name.toSnakeCase()) { - NBT.encodeToNbtTagRootless(MapSerializer(String.serializer(), serializer), default()) + SerializationManager.nbt.decodeFromNbtTagRootless(MapSerializer(String.serializer(), serializer), data.getOrPut(property.name.toSnakeCase()) { + SerializationManager.nbt.encodeToNbtTagRootless(MapSerializer(String.serializer(), serializer), default()) }) }.recover { val ret = default() - data[property.name.toSnakeCase()] = NBT.encodeToNbtTagRootless(MapSerializer(String.serializer(), serializer), ret) + data[property.name.toSnakeCase()] = SerializationManager.nbt.encodeToNbtTagRootless(MapSerializer(String.serializer(), serializer), ret) ret }.getOrThrow().toMutableMap()) { map -> setValue(thisRef, property, map) } } override fun setValue(thisRef: Any?, property: KProperty<*>, value: MutableMap) { - data[property.name.toSnakeCase()] = NBT.encodeToNbtTagRootless(MapSerializer(String.serializer(), serializer), value) + data[property.name.toSnakeCase()] = SerializationManager.nbt.encodeToNbtTagRootless(MapSerializer(String.serializer(), serializer), value) if (thisRef is BlockEntity) { if (property.name.toSnakeCase() in sync) @@ -299,6 +299,6 @@ class NBTHolderImpl : NBTHolder override fun updateProperty(propertyName: String, serializer: KSerializer, value: T) { - this.data[propertyName] = NBT.encodeToNbtTagRootless(serializer, value) + this.data[propertyName] = SerializationManager.nbt.encodeToNbtTagRootless(serializer, value) } } diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/SerializationManager.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/SerializationManager.kt index 20771408a..2979b388b 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/SerializationManager.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/SerializationManager.kt @@ -90,6 +90,15 @@ object SerializationManager { serializersModule = sharedModule } + /** + * The [SerializersModule] shared by [cbor]/[json]/[nbt], for code that needs a serializer + * without going through one of those formats - e.g. `module.serializer()` instead of the + * bare top-level `serializer()`, which silently ignores every contextual serializer + * registered here (ResourceLocation, BlockPos, etc. - see [MinecraftSerializersModule]) and + * falls back to raw reflection instead. + */ + val module: SerializersModule get() = sharedModule + /** The shared [Cbor] instance, reconfigured with [sharedModule] whenever [overwriteWith] or [invoke] runs. */ var cbor: Cbor = createCbor() private set diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/Utils.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/Utils.kt index eaa29989f..b847b9045 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/Utils.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/Utils.kt @@ -22,6 +22,11 @@ import net.minecraft.network.codec.StreamCodec import kotlin.reflect.KClass import com.google.gson.JsonElement as GsonElement +/** + * Gets data from a [RegistryFriendlyByteBuf] using the [KSerializer] resolved for its type. + */ +inline fun RegistryFriendlyByteBuf.read(): T = read(SerializationManager.module.serializer()) + /** * Gets data from a [RegistryFriendlyByteBuf] using the provided [KSerializer] */ @@ -29,10 +34,9 @@ fun RegistryFriendlyByteBuf.read(serializer: KSerializer): T = SerializationManager.cbor.decodeFromByteArray(serializer, readByteArray()) /** - * Writes data into a [RegistryFriendlyByteBuf] using the [KSerializer] using the class of the data + * Writes data into a [RegistryFriendlyByteBuf] using the [KSerializer] resolved for its type. */ -@OptIn(InternalSerializationApi::class) -fun RegistryFriendlyByteBuf.write(data: T) = write(data::class.serializer() as KSerializer, data) +inline fun RegistryFriendlyByteBuf.write(data: T) = write(SerializationManager.module.serializer(), data) /** * Writes data into a [RegistryFriendlyByteBuf] using a [KSerializer] diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/transfer/ArchieEnergyStorage.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/transfer/ArchieEnergyStorage.kt index c7139fd71..a2da30973 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/transfer/ArchieEnergyStorage.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/transfer/ArchieEnergyStorage.kt @@ -13,7 +13,7 @@ import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.decodeStructure import kotlinx.serialization.encoding.encodeStructure import net.benwoodworth.knbt.NbtTag -import net.kernelpanicsoft.archie.serialization.NBT +import net.kernelpanicsoft.archie.serialization.SerializationManager import net.kernelpanicsoft.archie.serialization.decodeFromNbtTagRootless import net.kernelpanicsoft.archie.serialization.encodeToNbtTagRootless import kotlin.math.min @@ -96,7 +96,7 @@ class ArchieEnergyStorage( } /** Snapshots this storage's [getCapacity] and stored amount as an [NbtTag], for save/sync. */ - override fun createSnapshot(): NbtTag = NBT.encodeToNbtTagRootless(serializer(), this) + override fun createSnapshot(): NbtTag = SerializationManager.nbt.encodeToNbtTagRootless(serializer(), this) /** * Restores this storage's capacity and stored amount from a snapshot produced by @@ -106,7 +106,7 @@ class ArchieEnergyStorage( */ override fun readSnapshot(snapshot: NbtTag) { - val decoded = NBT.decodeFromNbtTagRootless(serializer(), snapshot) + val decoded = SerializationManager.nbt.decodeFromNbtTagRootless(serializer(), snapshot) this.capacity = decoded.capacity.coerceAtLeast(0) this.amount = decoded.amount.coerceIn(0, this.capacity) } diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/transfer/ArchieFluidSlot.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/transfer/ArchieFluidSlot.kt index 501b2d518..15a1ef07c 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/transfer/ArchieFluidSlot.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/transfer/ArchieFluidSlot.kt @@ -19,7 +19,7 @@ import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.decodeStructure import kotlinx.serialization.encoding.encodeStructure import net.benwoodworth.knbt.NbtTag -import net.kernelpanicsoft.archie.serialization.NBT +import net.kernelpanicsoft.archie.serialization.SerializationManager import net.kernelpanicsoft.archie.serialization.decodeFromNbtTagRootless import net.kernelpanicsoft.archie.serialization.encodeToNbtTagRootless import net.kernelpanicsoft.archie.serialization.kSerializer @@ -134,7 +134,7 @@ class ArchieFluidSlot(private val limit: Long, private val onUpdate: () -> Unit override fun createSnapshot(): NbtTag { - return NBT.encodeToNbtTagRootless(serializer(), this) + return SerializationManager.nbt.encodeToNbtTagRootless(serializer(), this) } override fun update() @@ -144,7 +144,7 @@ class ArchieFluidSlot(private val limit: Long, private val onUpdate: () -> Unit override fun readSnapshot(snapshot: NbtTag) { - this.stack = NBT.decodeFromNbtTagRootless(serializer(), snapshot).stack + this.stack = SerializationManager.nbt.decodeFromNbtTagRootless(serializer(), snapshot).stack } /** Serializes an [ArchieFluidSlot] as its [limit] followed by its [ResourceStack] (or `null` when blank). */ diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/transfer/ArchieFluidStorage.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/transfer/ArchieFluidStorage.kt index b90779a55..eb8444752 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/transfer/ArchieFluidStorage.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/transfer/ArchieFluidStorage.kt @@ -16,7 +16,7 @@ import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.decodeStructure import kotlinx.serialization.encoding.encodeStructure import net.benwoodworth.knbt.NbtTag -import net.kernelpanicsoft.archie.serialization.NBT +import net.kernelpanicsoft.archie.serialization.SerializationManager import net.kernelpanicsoft.archie.serialization.decodeFromNbtTagRootless import net.kernelpanicsoft.archie.serialization.encodeToNbtTagRootless import net.minecraft.core.NonNullList @@ -68,7 +68,7 @@ open class ArchieFluidStorage private constructor( override fun createSnapshot(): NbtTag { - return NBT.encodeToNbtTagRootless(serializer(), this) + return SerializationManager.nbt.encodeToNbtTagRootless(serializer(), this) } override fun update() @@ -78,7 +78,7 @@ open class ArchieFluidStorage private constructor( override fun readSnapshot(snapshot: NbtTag) { - val slots = NBT.decodeFromNbtTagRootless(serializer(), snapshot).slots + val slots = SerializationManager.nbt.decodeFromNbtTagRootless(serializer(), snapshot).slots for (i in 0 until min(this.slots.size, slots.size)) { this.slots[i] = slots[i] diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/transfer/ArchieItemSlot.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/transfer/ArchieItemSlot.kt index 2bf6f01d3..8de73c3d4 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/transfer/ArchieItemSlot.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/transfer/ArchieItemSlot.kt @@ -1,6 +1,6 @@ package net.kernelpanicsoft.archie.transfer -import net.kernelpanicsoft.archie.serialization.NBT +import net.kernelpanicsoft.archie.serialization.SerializationManager import net.kernelpanicsoft.archie.serialization.decodeFromNbtTagRootless import net.kernelpanicsoft.archie.serialization.encodeToNbtTagRootless import earth.terrarium.common_storage_lib.resources.ResourceStack @@ -144,7 +144,7 @@ class ArchieItemSlot(private val onUpdate: () -> Unit = {}) : StorageSlot Unit = {}) : StorageSlot" }}"), dropShadow = false) Text(Component.literal("Choice dialog result: $pickedChoice"), dropShadow = false) - Button(onClick = { - layers.modal(dismissOnClickOutside = false) { - Panel(modifier = Modifier.size(196, 150)) { - Column(verticalArrangement = Arrangement.spacedBy(4)) { - Text(Component.literal("Modal Slot Scroll Test"), dropShadow = false) - Text(Component.literal("This modal should render above all base slots."), dropShadow = false, fontScale = 0.9f) - Scrollable(modifier = Modifier.size(186, 104)) { - Column(verticalArrangement = Arrangement.spacedBy(1)) { - repeat(14) { - Row(horizontalArrangement = Arrangement.spacedBy(1)) { - repeat(9) { - Surface(texture = "slot", modifier = Modifier.size(18, 18)) {} - } - } - } - } - } - Button(onClick = { dismiss() }, modifier = Modifier.width(80)) { - Text(Component.literal("Close"), dropShadow = false) - } - } - } - } - }) { - Text(Component.literal("Open Modal Slot Scroll Test"), dropShadow = false) - } Button(onClick = { layers.confirmDialog( title = Component.literal("Compose Layer Demo"), From f77c7687cb02a2b2caede98e774b1dd23ba6c2f9 Mon Sep 17 00:00:00 2001 From: KernelPanic Date: Wed, 12 Aug 2026 23:23:35 -0400 Subject: [PATCH 30/30] Fix modals not inheriting the root Theme, add regression coverage Each layer LayerStackManager pushes (modal, dropdown, tooltip) is its own top-level Composition parented directly to the screen's Recomposer, not nested under the base layer's tree - so LocalTheme.current inside a modal silently fell back to its own default ("java") instead of whatever Theme {} the base layer actually used, regardless of the screen's real theme. Theme {} now pushes its ThemeData onto LayerStackManager.themeStack (a mount-order stack, popped on dispose) whenever a LayerStackManager is in scope, via a new nullable-safe LocalLayerManagerOrNull local. Each screen's screenLocals wrapping (ComposeScreen/ComposeContainerScreen) re-supplies LocalTheme from the stack's top (LayerStackManager.rootTheme) to every layer it creates, so: - A plain root Theme {} now reaches every modal/dropdown/tooltip. - A nested Theme {} override sits on top of whatever it's nested inside while mounted, so a modal triggered from within it inherits the override, not the outer root - and reverts once the override unmounts. - A theme flipped at runtime propagates immediately (the stack is backed by mutableStateListOf). Added testModalInheritsRootTheme and testModalInheritsNestedThemeOverride to ModalComponentsGameTest, covering both cases directly (a modal reading LocalTheme.current, captured via SideEffect). Also folds Utils.kt's streamCodec through the read/write extensions added last commit instead of duplicating their cbor calls inline. Verified via full compile and the live runGametestClient suite, 25/25 (23 previous + 2 new). Co-Authored-By: Claude Sonnet 5 --- .../archie/gui/ComposeContainerScreen.kt | 16 ++++- .../archie/gui/ComposeScreen.kt | 16 ++++- .../archie/gui/layer/LayerStackManager.kt | 30 ++++++++ .../kernelpanicsoft/archie/gui/theme/Theme.kt | 22 +++++- .../archie/serialization/Utils.kt | 4 +- .../internal/tests/ModalComponentsGameTest.kt | 71 +++++++++++++++++++ 6 files changed, 153 insertions(+), 6 deletions(-) diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeContainerScreen.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeContainerScreen.kt index 6a22769d6..5f82cd7d1 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeContainerScreen.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeContainerScreen.kt @@ -19,11 +19,13 @@ import net.kernelpanicsoft.archie.gui.item.LocalItemState import net.kernelpanicsoft.archie.gui.layer.Layer import net.kernelpanicsoft.archie.gui.layer.LayerStackManager import net.kernelpanicsoft.archie.gui.layer.LocalLayerManager +import net.kernelpanicsoft.archie.gui.layer.LocalLayerManagerOrNull import net.kernelpanicsoft.archie.gui.layout.IntCoordinates import net.kernelpanicsoft.archie.gui.layout.IntRect import net.kernelpanicsoft.archie.gui.layout.LayoutNode import net.kernelpanicsoft.archie.gui.modifiers.Constraints import net.kernelpanicsoft.archie.gui.modifiers.input.PointerEventType +import net.kernelpanicsoft.archie.gui.theme.LocalTheme import net.kernelpanicsoft.archie.gui.util.extension.processCharEvent import net.kernelpanicsoft.archie.gui.util.extension.processDragEvent import net.kernelpanicsoft.archie.gui.util.extension.processKeyEvent @@ -163,7 +165,19 @@ abstract class ComposeContainerScreen>( LocalBlockEntityState provides (menu as? ComposeBlockContainerMenu<*, *>)?.blockEntityState, LocalItemState provides (menu as? ComposeItemContainerMenu<*>)?.itemState, LocalLayerManager provides layerManager, - ) { layerContent() } + LocalLayerManagerOrNull provides layerManager, + ) { + // Re-supplies whatever Theme{} is currently mounted in the base layer (see + // LayerStackManager.rootTheme) so a later-pushed layer isn't stuck with + // LocalTheme's own default - it's a separate top-level composition, so it'd + // never otherwise see a Theme{} that only wraps the base layer's own content. + val theme = layerManager.rootTheme + if (theme != null) { + CompositionLocalProvider(LocalTheme provides theme) { layerContent() } + } else { + layerContent() + } + } } AUIScopeManager.scopes += composeScope diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeScreen.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeScreen.kt index bc1a4f331..1cc0456f6 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeScreen.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/ComposeScreen.kt @@ -8,7 +8,9 @@ import net.kernelpanicsoft.archie.gui.focus.collectFocusableChildren import net.kernelpanicsoft.archie.gui.layer.Layer import net.kernelpanicsoft.archie.gui.layer.LayerStackManager import net.kernelpanicsoft.archie.gui.layer.LocalLayerManager +import net.kernelpanicsoft.archie.gui.layer.LocalLayerManagerOrNull import net.kernelpanicsoft.archie.gui.layout.* +import net.kernelpanicsoft.archie.gui.theme.LocalTheme import net.kernelpanicsoft.archie.gui.modifiers.Constraints import net.kernelpanicsoft.archie.gui.modifiers.Modifier import net.kernelpanicsoft.archie.gui.modifiers.fillMaxSize @@ -191,7 +193,19 @@ abstract class ComposeScreen( LocalScreen provides this, LocalVanillaScreen provides this, LocalLayerManager provides layerManager, - ) { layerContent() } + LocalLayerManagerOrNull provides layerManager, + ) { + // Re-supplies whatever Theme{} is currently mounted in the base layer (see + // LayerStackManager.rootTheme) so a later-pushed layer isn't stuck with + // LocalTheme's own default - it's a separate top-level composition, so it'd + // never otherwise see a Theme{} that only wraps the base layer's own content. + val theme = layerManager.rootTheme + if (theme != null) { + CompositionLocalProvider(LocalTheme provides theme) { layerContent() } + } else { + layerContent() + } + } } AUIScopeManager.scopes += composeScope diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/layer/LayerStackManager.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/layer/LayerStackManager.kt index ac35981c9..8a8ac322b 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/layer/LayerStackManager.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/layer/LayerStackManager.kt @@ -34,6 +34,7 @@ import net.kernelpanicsoft.archie.gui.modifiers.input.PointerEventType import net.kernelpanicsoft.archie.gui.modifiers.input.onPointerEvent import net.kernelpanicsoft.archie.gui.modifiers.position.offset import net.kernelpanicsoft.archie.gui.nodes.UINode +import net.kernelpanicsoft.archie.gui.theme.ThemeData import net.minecraft.network.chat.Component import java.util.* import kotlinx.coroutines.delay @@ -55,6 +56,14 @@ val LocalLayerManager = compositionLocalOf { error("No LayerManager provided. Are you inside a ComposeScreen?") } +/** + * Like [LocalLayerManager], but `null` instead of throwing when there's no [LayerStackManager] + * in scope - for code that wants to *opportunistically* interact with one (e.g. [net.kernelpanicsoft.archie.gui.theme.Theme] + * publishing [LayerStackManager.rootTheme]) without requiring every caller to run inside a + * [net.kernelpanicsoft.archie.gui.ComposeScreen]. + */ +val LocalLayerManagerOrNull = compositionLocalOf { null } + /** The depth index of the currently composed layer (base layer is `0`). */ val LocalLayerDepth = compositionLocalOf { 0 } @@ -104,6 +113,27 @@ class LayerStackManager( /** The ordered list of active layers. Layers are rendered bottom-to-top. */ val layers = mutableStateListOf() + /** + * Every [net.kernelpanicsoft.archie.gui.theme.Theme] scope currently mounted anywhere in + * this screen's base layer, in mount order (outermost first) - pushed/popped by + * [net.kernelpanicsoft.archie.gui.theme.Theme] itself via [LocalLayerManagerOrNull], so a + * nested `Theme {}` override sits on top of whatever it's nested inside while it's mounted, + * and the outer one resumes as [rootTheme] once it unmounts. See [rootTheme]. + */ + internal val themeStack = mutableStateListOf() + + /** + * The [ThemeData][net.kernelpanicsoft.archie.gui.theme.ThemeData] a newly-pushed layer + * (modal, dropdown, tooltip) should inherit: the innermost [net.kernelpanicsoft.archie.gui.theme.Theme] + * scope currently mounted in this screen's base layer, per [themeStack]. `null` until the + * base layer's first `Theme {}` mounts. Read back by the screen's `screenLocals` wrapping so + * every later-pushed layer inherits it too, instead of silently falling back to + * [net.kernelpanicsoft.archie.gui.theme.LocalTheme]'s own default - each later layer is its + * own top-level [androidx.compose.runtime.Composition] (see this class's own doc), so it + * would otherwise never see a `Theme {}` that only wraps the base layer's content. + */ + val rootTheme: ThemeData? get() = themeStack.lastOrNull() + /** * Represents the total size of the screen, calculated based on the dimensions of all active layers. * diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/theme/Theme.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/theme/Theme.kt index ecc8dd081..5cd91f30f 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/theme/Theme.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/gui/theme/Theme.kt @@ -2,6 +2,7 @@ package net.kernelpanicsoft.archie.gui.theme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.Immutable import androidx.compose.runtime.compositionLocalOf import kotlinx.serialization.SerialName @@ -9,6 +10,7 @@ import kotlinx.serialization.Serializable import net.kernelpanicsoft.archie.Archie import net.kernelpanicsoft.archie.resourcepacks.SerializationReloadListener import net.kernelpanicsoft.archie.serialization.SerializationManager +import net.kernelpanicsoft.archie.gui.layer.LocalLayerManagerOrNull import net.kernelpanicsoft.archie.gui.util.KColor import net.kernelpanicsoft.archie.util.div import net.kernelpanicsoft.archie.util.rem @@ -183,11 +185,27 @@ fun Theme( lightTextColor: KColor = KColor.WHITE, namespace: String = Archie.MOD_ID, content: @Composable () -> Unit, -) = CompositionLocalProvider(LocalTheme provides ThemeData(mode, type, darkTextColor, lightTextColor, namespace)) { content() } +) = Theme(ThemeData(mode, type, darkTextColor, lightTextColor, namespace), content) /** * Sets the active theme using a pre-built [ThemeData]. + * + * Also pushes [data] onto the nearest [net.kernelpanicsoft.archie.gui.layer.LayerStackManager]'s + * theme stack while mounted (popping it back off on dispose), so a modal/dropdown/tooltip pushed + * from anywhere in [content] inherits this theme too instead of silently falling back to + * [LocalTheme]'s own default - each later-pushed layer is its own top-level composition (see + * [net.kernelpanicsoft.archie.gui.layer.LayerStackManager]'s own doc) and would otherwise never + * see a `Theme {}` that only wraps the base layer's content. A nested `Theme {}` override stacks + * on top of whatever it's nested inside for as long as it stays mounted. */ @Composable -fun Theme(data: ThemeData, content: @Composable () -> Unit) = +fun Theme(data: ThemeData, content: @Composable () -> Unit) { + val layers = LocalLayerManagerOrNull.current + if (layers != null) { + DisposableEffect(data) { + layers.themeStack.add(data) + onDispose { layers.themeStack.remove(data) } + } + } CompositionLocalProvider(LocalTheme provides data) { content() } +} diff --git a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/Utils.kt b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/Utils.kt index b847b9045..f9ec2e292 100644 --- a/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/Utils.kt +++ b/core/common/src/main/kotlin/net/kernelpanicsoft/archie/serialization/Utils.kt @@ -96,8 +96,8 @@ val KSerializer.codec: Codec */ val KSerializer.streamCodec: StreamCodec get() = StreamCodec.of( - { buffer, value -> buffer.writeByteArray(SerializationManager.cbor.encodeToByteArray(this, value)) }, - { buffer -> SerializationManager.cbor.decodeFromByteArray(this, buffer.readByteArray()) } + { buffer, value -> buffer.write(this, value) }, + { buffer -> buffer.read(this) } ) /** diff --git a/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/ModalComponentsGameTest.kt b/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/ModalComponentsGameTest.kt index d61e6e524..4eac64a9d 100644 --- a/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/ModalComponentsGameTest.kt +++ b/gametest/common/src/main/kotlin/net/kernelpanicsoft/archie/gametest/internal/tests/ModalComponentsGameTest.kt @@ -1,5 +1,6 @@ package net.kernelpanicsoft.archie.gametest.internal.tests +import androidx.compose.runtime.SideEffect import net.kernelpanicsoft.archie.gametest.ClientGameTest import net.kernelpanicsoft.archie.gametest.ClientGameTestContext import net.kernelpanicsoft.archie.gametest.LayerSelector @@ -11,6 +12,9 @@ import net.kernelpanicsoft.archie.gui.composables.modal.ModalChoice import net.kernelpanicsoft.archie.gui.composables.theme.TextureStates import net.kernelpanicsoft.archie.gui.layer.LocalLayerManager import net.kernelpanicsoft.archie.gui.layout.Column +import net.kernelpanicsoft.archie.gui.nodes.UINode +import net.kernelpanicsoft.archie.gui.theme.LocalTheme +import net.kernelpanicsoft.archie.gui.theme.Theme import net.minecraft.network.chat.Component import org.lwjgl.glfw.GLFW import java.util.concurrent.atomic.AtomicBoolean @@ -180,6 +184,73 @@ class ModalComponentsGameTest { } } } + + @ClientGameTest + fun ClientGameTestContext.testModalInheritsRootTheme() { + // Regression check: a pushed modal is its own top-level Composition, parented directly + // to the screen's Recomposer rather than nested under the base layer's tree (see + // LayerStackManager's own doc) - so without LayerStackManager.rootTheme explicitly + // re-supplying it, LocalTheme.current inside a modal would silently fall back to its own + // default ("java") instead of whatever Theme{} the base layer actually used. + val capturedType = AtomicReference(null) + setScreen { ModalThemeProbeScreen(onModalThemeCaptured = { capturedType.set(it) }) } + waitForScreen { + assertEquals(1, layerCount) + baseLayer.rootNode { nodes("Button") }[0] { click() } // "Open Modal" + waitFor { _ -> layerCount == 2 } + waitForComposeIdle() + assertEquals("bedrock", capturedType.get()) { + "Expected the modal to inherit the root Theme's type instead of LocalTheme's own default" + } + } + } + + @ClientGameTest + fun ClientGameTestContext.testModalInheritsNestedThemeOverride() { + // A Theme{} nested inside another Theme{} pushes onto LayerStackManager.themeStack on + // top of the outer one while mounted - a modal triggered from within the nested override + // should inherit *that* (the innermost/most specific ambient theme), not the outer root. + val capturedType = AtomicReference(null) + setScreen { ModalThemeProbeScreen(nestedOverrideType = "java", onModalThemeCaptured = { capturedType.set(it) }) } + waitForScreen { + baseLayer.rootNode { nodes("Button") }[0] { click() } // "Open Modal", inside the nested override + waitFor { _ -> layerCount == 2 } + waitForComposeIdle() + assertEquals("java", capturedType.get()) { + "Expected the modal to inherit the nested Theme override, not the outer root theme" + } + } + } +} + +private class ModalThemeProbeScreen( + private val nestedOverrideType: String? = null, + private val onModalThemeCaptured: (String) -> Unit = {}, +) : ComposeScreen(Component.literal("Modal Theme Probe")) { + override fun init() { + super.init() + start { + Theme(type = "bedrock") { + val layers = LocalLayerManager.current + val openModal: (UINode) -> Unit = { + layers.modal { + val theme = LocalTheme.current + SideEffect { onModalThemeCaptured(theme.type) } + Text(Component.literal("Modal"), dropShadow = false) + } + } + Column { + if (nestedOverrideType != null) { + Theme(type = nestedOverrideType) { + Button(onClick = openModal) { Text(Component.literal("Open Modal"), dropShadow = false) } + } + } else { + Button(onClick = openModal) { Text(Component.literal("Open Modal"), dropShadow = false) } + } + } + } + } + } } private class ModalComponentsProbeScreen(