Skip to content

Commit a45941e

Browse files
committed
Implement splicing table keyboard shortcuts
1 parent 5c05d00 commit a45941e

File tree

10 files changed

+201
-15
lines changed

10 files changed

+201
-15
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
66

77
## [UNRELEASED]
88

9+
### Added
10+
11+
- Added keyboard shortcuts to the Splicing Table, configurable in HexDebug's client config menu.
12+
913
### Fixed
1014

1115
- Fixed an issue where newly placed splicing tables would default to having the first iota selected.

Common/src/main/java/gay/object/hexdebug/mixin/HexDebugMixinConfigPlugin.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package gay.object.hexdebug.mixin;
22

3+
import dev.architectury.platform.Platform;
34
import gay.object.hexdebug.HexDebug;
45
import org.objectweb.asm.tree.ClassNode;
56
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
@@ -27,6 +28,10 @@ public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
2728
}
2829
return shouldApply;
2930
}
31+
if (mixinClassName.startsWith("gay.object.hexdebug.mixin.interop.")) {
32+
var id = mixinClassName.substring("gay.object.hexdebug.mixin.interop.".length()).split("\\.", 2)[0];
33+
return Platform.isModLoaded(id);
34+
}
3035
return true;
3136
}
3237

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package gay.object.hexdebug.mixin.interop.emi;
2+
3+
import com.llamalad7.mixinextras.expression.Definition;
4+
import com.llamalad7.mixinextras.expression.Expression;
5+
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
6+
import com.llamalad7.mixinextras.sugar.Local;
7+
import dev.emi.emi.screen.EmiScreenManager;
8+
import gay.object.hexdebug.gui.splicing.SplicingTableScreen;
9+
import net.minecraft.client.Minecraft;
10+
import org.spongepowered.asm.mixin.Mixin;
11+
import org.spongepowered.asm.mixin.injection.At;
12+
13+
@Mixin(value = EmiScreenManager.class, remap = false)
14+
public abstract class MixinEmiScreenManager {
15+
@Definition(id = "keyCode", local = @Local(name = "keyCode", type = int.class))
16+
@Expression("keyCode == 89")
17+
@ModifyExpressionValue(method = "keyPressed", at = @At(value = "MIXINEXTRAS:EXPRESSION"), require = 0)
18+
private static boolean hexdebug$cancelHardcodedKeybindInSplicingTable(boolean original) {
19+
if (Minecraft.getInstance().screen instanceof SplicingTableScreen) {
20+
return false;
21+
}
22+
return original;
23+
}
24+
}

Common/src/main/kotlin/gay/object/hexdebug/config/HexDebugClientConfig.kt

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
package gay.`object`.hexdebug.config
22

3+
import at.petrak.hexcasting.api.utils.asTranslatedComponent
4+
import com.mojang.blaze3d.platform.InputConstants
35
import gay.`object`.hexdebug.HexDebug
6+
import gay.`object`.hexdebug.gui.splicing.SplicingTableScreen
47
import me.shedaniel.autoconfig.AutoConfig
58
import me.shedaniel.autoconfig.ConfigData
69
import me.shedaniel.autoconfig.ConfigHolder
710
import me.shedaniel.autoconfig.annotation.Config
811
import me.shedaniel.autoconfig.annotation.ConfigEntry.Category
9-
import me.shedaniel.autoconfig.annotation.ConfigEntry.Gui.Tooltip
10-
import me.shedaniel.autoconfig.annotation.ConfigEntry.Gui.TransitiveObject
12+
import me.shedaniel.autoconfig.annotation.ConfigEntry.Gui.*
1113
import me.shedaniel.autoconfig.serializer.PartitioningSerializer
1214
import me.shedaniel.autoconfig.serializer.PartitioningSerializer.GlobalData
1315
import me.shedaniel.autoconfig.serializer.Toml4jConfigSerializer
16+
import me.shedaniel.autoconfig.util.Utils.getUnsafely
17+
import me.shedaniel.autoconfig.util.Utils.setUnsafely
18+
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder
19+
import me.shedaniel.clothconfig2.api.Modifier
20+
import me.shedaniel.clothconfig2.api.ModifierKeyCode
1421
import net.minecraft.world.InteractionResult
1522

1623
object HexDebugClientConfig {
@@ -21,6 +28,30 @@ object HexDebugClientConfig {
2128
val config get() = holder.config.client
2229

2330
fun init() {
31+
AutoConfig.getGuiRegistry(GlobalConfig::class.java).apply {
32+
val entryBuilder = ConfigEntryBuilder.create()
33+
34+
// ModifierKeyCode
35+
registerTypeProvider(
36+
{ i18n, field, config, defaults, _ -> listOf(
37+
entryBuilder.startModifierKeyCodeField(
38+
when (config) {
39+
is ClientConfig.SplicingTableKeybinds,
40+
is ClientConfig.EnlightenedSplicingTableKeybinds -> {
41+
SplicingTableScreen.buttonText(field.name.camelToSnakeCase())
42+
}
43+
else -> i18n.asTranslatedComponent
44+
},
45+
getUnsafely(field, config, ConfigModifierKey(InputConstants.UNKNOWN.name)).inner,
46+
)
47+
.setModifierDefaultValue { (getUnsafely(field, defaults) as ConfigModifierKey).inner }
48+
.setModifierSaveConsumer { setUnsafely(field, config, ConfigModifierKey(it)) }
49+
.build()
50+
) },
51+
ConfigModifierKey::class.java,
52+
)
53+
}
54+
2455
holder = AutoConfig.register(
2556
GlobalConfig::class.java,
2657
PartitioningSerializer.wrap(::Toml4jConfigSerializer),
@@ -63,11 +94,82 @@ object HexDebugClientConfig {
6394

6495
@Tooltip
6596
val invertSplicingTableScrollDirection: Boolean = false
97+
98+
@Tooltip
99+
@CollapsibleObject
100+
val splicingTableKeybinds = SplicingTableKeybinds()
101+
102+
@Tooltip
103+
@CollapsibleObject
104+
val enlightenedSplicingTableKeybinds = EnlightenedSplicingTableKeybinds()
105+
106+
class SplicingTableKeybinds {
107+
val selectNone = ConfigModifierKey(InputConstants.KEY_A, ctrl = true, shift = true)
108+
val selectAll = ConfigModifierKey(InputConstants.KEY_A, ctrl = true)
109+
val undo = ConfigModifierKey(InputConstants.KEY_Z, ctrl = true)
110+
val redo = ConfigModifierKey(InputConstants.KEY_Y, ctrl = true)
111+
val nudgeLeft = ConfigModifierKey(InputConstants.KEY_LEFT, ctrl = true)
112+
val nudgeRight = ConfigModifierKey(InputConstants.KEY_RIGHT, ctrl = true)
113+
val duplicate = ConfigModifierKey(InputConstants.KEY_D, ctrl = true)
114+
val delete = ConfigModifierKey(InputConstants.KEY_DELETE)
115+
val cut = ConfigModifierKey(InputConstants.KEY_X, ctrl = true)
116+
val copy = ConfigModifierKey(InputConstants.KEY_C, ctrl = true)
117+
val pasteSplat = ConfigModifierKey(InputConstants.KEY_V, ctrl = true)
118+
val pasteVerbatim = ConfigModifierKey(InputConstants.KEY_V, ctrl = true, shift = true)
119+
}
120+
121+
class EnlightenedSplicingTableKeybinds {
122+
val cast = ConfigModifierKey(InputConstants.KEY_RETURN, ctrl = true)
123+
}
66124
}
67125
}
68126

69127
enum class DebuggerDisplayMode {
70128
DISABLED,
71129
NOT_CONNECTED,
72130
ENABLED,
73-
}
131+
}
132+
133+
// NOTE: this must have a no-arg constructor
134+
// otherwise, Gson uses unsafe allocation to construct it, which breaks the lazy property
135+
data class ConfigModifierKey(
136+
val key: String = InputConstants.UNKNOWN.name,
137+
val alt: Boolean = false,
138+
val ctrl: Boolean = false,
139+
val shift: Boolean = false,
140+
) {
141+
constructor(
142+
keyCode: Int,
143+
alt: Boolean = false,
144+
ctrl: Boolean = false,
145+
shift: Boolean = false,
146+
) : this(
147+
key = InputConstants.Type.KEYSYM.getOrCreate(keyCode).name,
148+
alt = alt,
149+
ctrl = ctrl,
150+
shift = shift,
151+
)
152+
153+
constructor(inner: ModifierKeyCode) : this(
154+
key = inner.keyCode.name,
155+
alt = inner.modifier.hasAlt(),
156+
ctrl = inner.modifier.hasControl(),
157+
shift = inner.modifier.hasShift(),
158+
)
159+
160+
// transient to make Gson and Toml4j ignore the backing field
161+
@delegate:Transient
162+
val inner: ModifierKeyCode by lazy {
163+
ModifierKeyCode.of(
164+
InputConstants.getKey(key),
165+
Modifier.of(alt, ctrl, shift),
166+
)
167+
}
168+
169+
fun matchesKey(keyCode: Int, scanCode: Int) = inner.matchesKey(keyCode, scanCode)
170+
}
171+
172+
// https://stackoverflow.com/a/60010299
173+
private val camelRegex = "(?<=[a-zA-Z])[A-Z]".toRegex()
174+
175+
private fun String.camelToSnakeCase() = replace(camelRegex) { "_${it.value}" }.lowercase()

Common/src/main/kotlin/gay/object/hexdebug/gui/splicing/SplicingTableScreen.kt

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import net.minecraft.client.gui.components.AbstractButton
2121
import net.minecraft.client.gui.components.Renderable
2222
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
2323
import net.minecraft.client.renderer.RenderType
24+
import net.minecraft.client.resources.sounds.SimpleSoundInstance
2425
import net.minecraft.network.chat.Component
26+
import net.minecraft.sounds.SoundEvents
2527
import net.minecraft.world.InteractionHand
2628
import net.minecraft.world.entity.player.Inventory
2729
import java.awt.Color
@@ -120,6 +122,8 @@ class SplicingTableScreen(
120122

121123
private var castingCooldown = 0
122124

125+
private val canCastIgnoringCooldown get() = data.isEnlightened && data.hasHex && hasMediaForAction
126+
123127
override fun init() {
124128
super.init()
125129

@@ -332,7 +336,7 @@ class SplicingTableScreen(
332336

333337
*listOf(
334338
SplicingTableAction.PASTE_SPLAT to false,
335-
SplicingTableAction.PASTE to true,
339+
SplicingTableAction.PASTE_VERBATIM to true,
336340
).map { (action, needsShiftDown) ->
337341
object : SpriteButton(
338342
x = leftPos + 27,
@@ -392,8 +396,6 @@ class SplicingTableScreen(
392396
castingCooldown = maxCastingCooldown
393397
},
394398
) {
395-
private val canCastIgnoringCooldown get() = data.hasHex && hasMediaForAction
396-
397399
override val uOffsetHovered get() = uOffset
398400
override val vOffsetHovered get() = vOffset + 32
399401

@@ -535,13 +537,53 @@ class SplicingTableScreen(
535537
}
536538

537539
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
538-
if (super.keyPressed(keyCode, scanCode, modifiers)) return true
540+
// AbstractContainerScreen.keyPressed always returns true, so check our keys first
541+
if (keyPressedInner(keyCode, scanCode)) return true
542+
return super.keyPressed(keyCode, scanCode, modifiers)
543+
}
539544

545+
private fun keyPressedInner(keyCode: Int, scanCode: Int): Boolean {
546+
if (
547+
HexDebugClientConfig.config.enlightenedSplicingTableKeybinds.cast.matchesKey(keyCode, scanCode)
548+
&& canCastIgnoringCooldown
549+
&& castingCooldown <= 0
550+
) {
551+
menu.table.castHex(null)
552+
castingCooldown = maxCastingCooldown
553+
playButtonClick()
554+
return true
555+
}
540556

557+
val action = HexDebugClientConfig.config.splicingTableKeybinds.run {
558+
when {
559+
selectNone.matchesKey(keyCode, scanCode) -> SplicingTableAction.SELECT_NONE
560+
selectAll.matchesKey(keyCode, scanCode) -> SplicingTableAction.SELECT_ALL
561+
undo.matchesKey(keyCode, scanCode) -> SplicingTableAction.UNDO
562+
redo.matchesKey(keyCode, scanCode) -> SplicingTableAction.REDO
563+
nudgeLeft.matchesKey(keyCode, scanCode) -> SplicingTableAction.NUDGE_LEFT
564+
nudgeRight.matchesKey(keyCode, scanCode) -> SplicingTableAction.NUDGE_RIGHT
565+
duplicate.matchesKey(keyCode, scanCode) -> SplicingTableAction.DUPLICATE
566+
delete.matchesKey(keyCode, scanCode) -> SplicingTableAction.DELETE
567+
cut.matchesKey(keyCode, scanCode) -> SplicingTableAction.CUT
568+
copy.matchesKey(keyCode, scanCode) -> SplicingTableAction.COPY
569+
pasteSplat.matchesKey(keyCode, scanCode) -> SplicingTableAction.PASTE_SPLAT
570+
pasteVerbatim.matchesKey(keyCode, scanCode) -> SplicingTableAction.PASTE_VERBATIM
571+
else -> return false
572+
}
573+
}
541574

575+
if (data.isListReadable && action.test()) {
576+
action.onPress()
577+
playButtonClick()
578+
return true
579+
}
542580
return false
543581
}
544582

583+
private fun playButtonClick() {
584+
minecraft!!.soundManager.play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1f))
585+
}
586+
545587
// TODO: limit scroll to certain regions? (let's see if anyone complains first)
546588
override fun mouseScrolled(mouseX: Double, mouseY: Double, delta: Double): Boolean {
547589
if (super.mouseScrolled(mouseX, mouseY, delta)) return true

Common/src/main/kotlin/gay/object/hexdebug/splicing/SplicingTableAction.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ enum class SplicingTableAction(val value: Value<*>) {
194194

195195
// rw list, read clipboard
196196

197-
PASTE(Value(ReadWriteListFromClipboard, consumesMedia = true) {
197+
PASTE_VERBATIM(Value(ReadWriteListFromClipboard, consumesMedia = true) {
198198
typedSelection.mutableSubList(list).apply {
199199
clear()
200200
add(clipboard)

Common/src/main/resources/assets/hexdebug/lang/en_us.flatten.json5

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
redo: "Redo",
4040
cut: "Cut",
4141
copy: "Copy",
42-
paste: "Paste (Verbatim)",
42+
paste_verbatim: "Paste (Verbatim)",
4343
paste_splat: "Paste (Flattened)",
4444
export: "Export as .hexpattern",
4545
cast: "Activate Table",
@@ -87,7 +87,15 @@
8787
invertSplicingTableScrollDirection: {
8888
"": "Invert Splicing Table Scroll Direction",
8989
"@Tooltip": "Whether scrolling up (as opposed to down) will increase the view index of the splicing table, and vice versa.",
90-
}
90+
},
91+
splicingTableKeybinds: {
92+
"": "Splicing Table Keybinds",
93+
"@Tooltip": "Keybinds for actions in the Splicing Table GUI.",
94+
},
95+
enlightenedSplicingTableKeybinds: {
96+
"": "Mindsplice Table Keybinds",
97+
"@Tooltip": "Keybinds for actions in the Mindsplice Table GUI.",
98+
},
9199
},
92100
server: {
93101
maxUndoStackSize: {

Common/src/main/resources/assets/hexdebug/lang/ru_ru.flatten.json5

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
redo: "Вернуть",
3535
cut: "Вырезать",
3636
copy: "Копировать",
37-
paste: "Вставить (Полный)",
37+
paste_verbatim: "Вставить (Полный)",
3838
paste_splat: "Вставить (Плоский)",
3939
export: "Экспортировать как .hexpattern",
4040
},

Common/src/main/resources/assets/hexdebug/lang/zh_cn.flatten.json5

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
redo: "重做",
4040
cut: "剪切",
4141
copy: "复制",
42-
paste: "粘贴(逐项)",
42+
paste_verbatim: "粘贴(逐项)",
4343
paste_splat: "粘贴(扁平化)",
4444
export: "导出为.hexpattern文件",
4545
cast: "激活编辑台",

Common/src/main/resources/hexdebug-common.mixins.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@
1313
"MixinOpEval",
1414
"MixinOpImpetusDir",
1515
"MixinOpImpetusPos",
16-
"MixinStaffCastEnv"
16+
"MixinStaffCastEnv",
17+
"interop.emi.MixinEmiScreenManager"
1718
],
1819
"plugin": "gay.object.hexdebug.mixin.HexDebugMixinConfigPlugin",
19-
"injectors": {
20-
"defaultRequire": 1
20+
"mixinextras": {
21+
"minVersion": "0.5.0"
2122
}
2223
}

0 commit comments

Comments
 (0)