Skip to content

Commit 40aa826

Browse files
committed
Implement splicing table rainbow brackets
1 parent 22acfbe commit 40aa826

File tree

15 files changed

+295
-31
lines changed

15 files changed

+295
-31
lines changed

CHANGELOG.md

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

1111
- Added a new resource-pack-based data-driven system for addon devs to customize how their iotas are rendered in the Splicing Table.
1212
- Added Hexical interop to make the telepathy and notebook keys work in the Splicing/Mindsplice Table GUI.
13+
- Added support for "rainbow brackets" (also known as bracket pair colorization) to the Splicing Table. Introspection and Retrospection are now tinted rainbow colors based on their depth. The list of colors is configurable in HexDebug's client settings, and there's also an option to disable the feature entirely.
1314

1415
### Changed
1516

Common/src/main/java/gay/object/hexdebug/api/client/splicing/SplicingTableIotaRenderer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55

66
@FunctionalInterface
77
public interface SplicingTableIotaRenderer {
8-
void render(@NotNull GuiGraphics guiGraphics, int x, int y);
8+
void render(@NotNull GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick);
99
}

Common/src/main/java/gay/object/hexdebug/api/client/splicing/SplicingTableIotaRendererProvider.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import at.petrak.hexcasting.api.casting.iota.IotaType;
44
import at.petrak.hexcasting.common.lib.hex.HexIotaTypes;
5+
import com.google.common.collect.Lists;
56
import gay.object.hexdebug.api.splicing.SplicingTableIotaClientView;
67
import gay.object.hexdebug.gui.splicing.SplicingTableScreen;
78
import net.minecraft.network.chat.Component;
@@ -20,7 +21,9 @@ public interface SplicingTableIotaRendererProvider {
2021
@NotNull
2122
SplicingTableIotaRenderer createRenderer(
2223
@NotNull IotaType<?> type,
23-
@NotNull SplicingTableIotaClientView iota
24+
@NotNull SplicingTableIotaClientView iota,
25+
int x,
26+
int y
2427
);
2528

2629
/**
@@ -32,25 +35,31 @@ SplicingTableIotaRenderer createRenderer(
3235
@NotNull
3336
default SplicingTableIotaTooltip createTooltip(
3437
@NotNull IotaType<?> type,
35-
@NotNull SplicingTableIotaClientView iota,
36-
int index
38+
@NotNull SplicingTableIotaClientView iota
3739
) {
38-
ArrayList<Component> advanced = new ArrayList<>();
40+
ArrayList<Component> advanced = new ArrayList<>();
3941
var typeKey = HexIotaTypes.REGISTRY.getKey(type);
4042
if (typeKey != null) {
4143
advanced.add(Component.literal(typeKey.toString()));
4244
}
45+
advanced.add(SplicingTableScreen.tooltipText("depth", iota.depth()));
46+
4347
return new SplicingTableIotaTooltip(
4448
iota.name(),
4549
new ArrayList<>(),
46-
new ArrayList<>(List.of(SplicingTableScreen.tooltipText("index", index))),
50+
Lists.newArrayList(
51+
SplicingTableScreen.tooltipText("index", iota.index())
52+
),
4753
advanced,
4854
null
4955
);
5056
}
5157

5258
/**
5359
* Returns the background type for this renderer.
60+
* <br>
61+
* This is called every time the splicing table changes which iotas are currently visible, so
62+
* don't do anything too laggy in here.
5463
*/
5564
@NotNull
5665
default SplicingTableIotaBackgroundType getBackgroundType(

Common/src/main/java/gay/object/hexdebug/api/splicing/SplicingTableIotaClientView.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,44 @@
88
import net.minecraft.nbt.CompoundTag;
99
import net.minecraft.nbt.Tag;
1010
import net.minecraft.network.chat.Component;
11+
import org.jetbrains.annotations.ApiStatus;
1112
import org.jetbrains.annotations.NotNull;
1213
import org.jetbrains.annotations.Nullable;
1314

15+
/**
16+
* A client-side representation of an iota in the Splicing Table's main list.
17+
* <br>
18+
* NOTE: Consumers should not construct this class themselves, as its fields may change at any time.
19+
* All constructors are annotated with {@link ApiStatus.Internal} to indicate this.
20+
* @param tag The raw iota NBT.
21+
* @param name The formatted name of the iota for use in tooltips.
22+
* @param hexpatternSource The iota converted to a plain string in {@code .hexpattern} format.
23+
* @param index The index of this iota in the list.
24+
* @param depth The number of unclosed Introspection patterns preceding this iota.
25+
*/
1426
public record SplicingTableIotaClientView(
1527
@NotNull CompoundTag tag,
1628
@NotNull Component name,
17-
@NotNull String hexpatternSource
29+
@NotNull String hexpatternSource,
30+
int index,
31+
int depth
1832
) {
19-
public SplicingTableIotaClientView(@NotNull Iota iota, @NotNull CastingEnvironment env) {
33+
@ApiStatus.Internal
34+
public SplicingTableIotaClientView {}
35+
36+
@ApiStatus.Internal
37+
public SplicingTableIotaClientView(
38+
@NotNull Iota iota,
39+
@NotNull CastingEnvironment env,
40+
int index,
41+
int depth
42+
) {
2043
this(
2144
IotaType.serialize(iota),
2245
ExtensionsKt.displayWithPatternName(iota, env),
23-
ExtensionsKt.toHexpatternSource(iota, env)
46+
ExtensionsKt.toHexpatternSource(iota, env),
47+
index,
48+
depth
2449
);
2550
}
2651

Common/src/main/kotlin/gay/object/hexdebug/blocks/splicing/SplicingTableBlockEntity.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import at.petrak.hexcasting.api.addldata.ADMediaHolder
66
import at.petrak.hexcasting.api.block.HexBlockEntity
77
import at.petrak.hexcasting.api.casting.ParticleSpray
88
import at.petrak.hexcasting.api.casting.eval.ResolvedPatternType
9+
import at.petrak.hexcasting.api.casting.eval.SpecialPatterns
910
import at.petrak.hexcasting.api.casting.eval.vm.CastingVM
1011
import at.petrak.hexcasting.api.casting.iota.Iota
1112
import at.petrak.hexcasting.api.casting.iota.IotaType
@@ -30,6 +31,7 @@ import gay.`object`.hexdebug.splicing.*
3031
import gay.`object`.hexdebug.utils.Option.None
3132
import gay.`object`.hexdebug.utils.Option.Some
3233
import gay.`object`.hexdebug.utils.setPropertyIfChanged
34+
import gay.`object`.hexdebug.utils.sigsEqual
3335
import net.minecraft.core.BlockPos
3436
import net.minecraft.nbt.CompoundTag
3537
import net.minecraft.nbt.ListTag
@@ -201,8 +203,14 @@ class SplicingTableBlockEntity(pos: BlockPos, state: BlockState) :
201203

202204
override fun getClientView() = getData(null)?.run {
203205
val env = FakeCastEnv(level)
206+
var depth = 0
204207
SplicingTableClientView(
205-
list = list?.map { SplicingTableIotaClientView(it, env) },
208+
list = list?.mapIndexed { index, iota ->
209+
if ((iota as? PatternIota)?.pattern.sigsEqual(SpecialPatterns.RETROSPECTION)) depth--
210+
val view = SplicingTableIotaClientView(iota, env, index, depth)
211+
if ((iota as? PatternIota)?.pattern.sigsEqual(SpecialPatterns.INTROSPECTION)) depth++
212+
view
213+
},
206214
clipboard = clipboard?.let { IotaType.serialize(it) },
207215
isListWritable = listWriter != null,
208216
isClipboardWritable = clipboardWriter != null,

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

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ package gay.`object`.hexdebug.config
33
import at.petrak.hexcasting.api.utils.asTranslatedComponent
44
import com.mojang.blaze3d.platform.InputConstants
55
import gay.`object`.hexdebug.HexDebug
6+
import gay.`object`.hexdebug.gui.config.startColorList
67
import gay.`object`.hexdebug.gui.splicing.SplicingTableScreen
78
import gay.`object`.hexdebug.splicing.SplicingTableAction
89
import me.shedaniel.autoconfig.AutoConfig
910
import me.shedaniel.autoconfig.ConfigData
1011
import me.shedaniel.autoconfig.ConfigHolder
1112
import me.shedaniel.autoconfig.annotation.Config
1213
import me.shedaniel.autoconfig.annotation.ConfigEntry.Category
14+
import me.shedaniel.autoconfig.annotation.ConfigEntry.ColorPicker
1315
import me.shedaniel.autoconfig.annotation.ConfigEntry.Gui.*
1416
import me.shedaniel.autoconfig.serializer.PartitioningSerializer
1517
import me.shedaniel.autoconfig.serializer.PartitioningSerializer.GlobalData
@@ -20,6 +22,7 @@ import me.shedaniel.clothconfig2.api.ConfigEntryBuilder
2022
import me.shedaniel.clothconfig2.api.Modifier
2123
import me.shedaniel.clothconfig2.api.ModifierKeyCode
2224
import net.minecraft.world.InteractionResult
25+
import java.lang.reflect.ParameterizedType
2326

2427
object HexDebugClientConfig {
2528
@JvmStatic
@@ -34,7 +37,7 @@ object HexDebugClientConfig {
3437

3538
// ModifierKeyCode
3639
registerTypeProvider(
37-
{ i18n, field, config, defaults, _ -> listOf(
40+
{ i18n, field, config, defaults, _ ->
3841
entryBuilder.startModifierKeyCodeField(
3942
when (config) {
4043
is ClientConfig.SplicingTableKeybinds,
@@ -48,9 +51,28 @@ object HexDebugClientConfig {
4851
.setModifierDefaultValue { (getUnsafely(field, defaults) as ConfigModifierKey).inner }
4952
.setModifierSaveConsumer { setUnsafely(field, config, ConfigModifierKey(it)) }
5053
.build()
51-
) },
54+
.toList()
55+
},
5256
ConfigModifierKey::class.java,
5357
)
58+
59+
// list of color pickers
60+
registerAnnotationProvider(
61+
{ i18n, field, config, defaults, _ ->
62+
entryBuilder.startColorList(i18n.asTranslatedComponent, getUnsafely(field, config))
63+
.setDefaultValue { getUnsafely(field, defaults) }
64+
.setSaveConsumer { setUnsafely(field, config, it) }
65+
.build()
66+
.toList()
67+
},
68+
{ field ->
69+
val typeArgs = (field.genericType as? ParameterizedType)?.actualTypeArguments
70+
List::class.java.isAssignableFrom(field.type)
71+
&& typeArgs?.size == 1
72+
&& typeArgs[0] == Integer::class.java
73+
},
74+
ColorPicker::class.java,
75+
)
5476
}
5577

5678
holder = AutoConfig.register(
@@ -96,6 +118,21 @@ object HexDebugClientConfig {
96118
@Tooltip
97119
val invertSplicingTableScrollDirection: Boolean = false
98120

121+
@Tooltip
122+
val enableSplicingTableRainbowBrackets: Boolean = true
123+
124+
// split Turbo into 8 samples, took the middle 6
125+
@Tooltip
126+
@ColorPicker
127+
val rainbowBracketColors: List<Int> = listOf(
128+
0xda3907,
129+
0xfe9b2d,
130+
0xd1e934,
131+
0x62fc6b,
132+
0x1bcfd5,
133+
0x4676ee,
134+
)
135+
99136
@Tooltip
100137
@CollapsibleObject
101138
val splicingTableKeybinds = SplicingTableKeybinds()
@@ -243,3 +280,5 @@ data class ConfigModifierKey(
243280
private val camelRegex = "(?<=[a-zA-Z])[A-Z]".toRegex()
244281

245282
private fun String.camelToSnakeCase() = replace(camelRegex) { "_${it.value}" }.lowercase()
283+
284+
private fun <T> T.toList() = listOf(this)
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package gay.`object`.hexdebug.gui.config
2+
3+
import at.petrak.hexcasting.api.utils.asTranslatedComponent
4+
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder
5+
import me.shedaniel.clothconfig2.gui.entries.AbstractTextFieldListListEntry
6+
import me.shedaniel.clothconfig2.gui.widget.ColorDisplayWidget
7+
import me.shedaniel.clothconfig2.impl.builders.AbstractListBuilder
8+
import net.minecraft.client.Minecraft
9+
import net.minecraft.client.gui.GuiGraphics
10+
import net.minecraft.network.chat.Component
11+
import java.util.*
12+
import java.util.function.Consumer
13+
import java.util.function.Supplier
14+
15+
// note: alpha support is not implemented
16+
class ColorListListEntry(
17+
fieldName: Component,
18+
value: List<Int>,
19+
defaultExpanded: Boolean,
20+
tooltipSupplier: Supplier<Optional<Array<Component>>>?,
21+
saveConsumer: Consumer<List<Int>>,
22+
defaultValue: Supplier<List<Int>>?,
23+
resetButtonKey: Component,
24+
requiresRestart: Boolean = false,
25+
deleteButtonEnabled: Boolean = true,
26+
insertInFront: Boolean = true,
27+
) : AbstractTextFieldListListEntry<Int, ColorListListEntry.ColorListCell, ColorListListEntry>(
28+
fieldName,
29+
value,
30+
defaultExpanded,
31+
tooltipSupplier,
32+
saveConsumer,
33+
defaultValue,
34+
resetButtonKey,
35+
requiresRestart,
36+
deleteButtonEnabled,
37+
insertInFront,
38+
::ColorListCell,
39+
) {
40+
override fun self() = this
41+
42+
class ColorListCell(
43+
value: Int,
44+
listListEntry: ColorListListEntry,
45+
) : AbstractTextFieldListCell<Int, ColorListCell, ColorListListEntry>(
46+
value,
47+
listListEntry,
48+
) {
49+
init {
50+
widget.value = "#" + value.toUInt().toString(16)
51+
}
52+
53+
private val colorWidgetSize = widget.height
54+
private val colorWidget = ColorDisplayWidget(widget, 0, 0, colorWidgetSize, getValue())
55+
56+
override fun render(
57+
graphics: GuiGraphics,
58+
index: Int,
59+
y: Int,
60+
x: Int,
61+
entryWidth: Int,
62+
entryHeight: Int,
63+
mouseX: Int,
64+
mouseY: Int,
65+
isSelected: Boolean,
66+
delta: Float
67+
) {
68+
val textX = if (Minecraft.getInstance().font.isBidirectional) {
69+
colorWidget.x = x + entryWidth - colorWidgetSize
70+
x
71+
} else {
72+
colorWidget.x = x
73+
x + colorWidgetSize + 3
74+
}
75+
super.render(graphics, index, y, textX, entryWidth - colorWidgetSize - 3, entryHeight, mouseX, mouseY, isSelected, delta)
76+
colorWidget.y = y - 4
77+
colorWidget.setColor(0xff000000.toInt() or value)
78+
colorWidget.render(graphics, mouseX, mouseY, delta)
79+
}
80+
81+
override fun getError(): Optional<Component> {
82+
if (unsignedValue == null) {
83+
return Optional.of("text.cloth-config.error.color.invalid_color".asTranslatedComponent)
84+
}
85+
return Optional.empty()
86+
}
87+
88+
override fun getValue(): Int = unsignedValue?.toInt() ?: -1
89+
90+
// this must return valid for "" and "#", otherwise you can't clear the field
91+
override fun isValidText(text: String): Boolean =
92+
text.removePrefix("#").isEmpty() || parseColor(text) != null
93+
94+
override fun substituteDefault(value: Int?): Int = value ?: 0
95+
96+
private val unsignedValue get() = parseColor(widget.value)
97+
98+
private fun parseColor(color: String): UInt? {
99+
return try {
100+
if (color.startsWith("#")) {
101+
color.removePrefix("#").takeIf { it.length <= 6 }?.toUInt(16)
102+
} else {
103+
color.toUInt()
104+
}?.takeIf { it <= 0xffffffu }
105+
} catch (e: NumberFormatException) {
106+
null
107+
}
108+
}
109+
}
110+
}
111+
112+
class ColorListBuilder(
113+
resetButtonKey: Component,
114+
fieldNameKey: Component,
115+
) : AbstractListBuilder<Int, ColorListListEntry, ColorListBuilder>(
116+
resetButtonKey,
117+
fieldNameKey,
118+
) {
119+
constructor(
120+
resetButtonKey: Component,
121+
fieldNameKey: Component,
122+
value: List<Int>,
123+
) : this(resetButtonKey, fieldNameKey) {
124+
this.value = value
125+
}
126+
127+
override fun build(): ColorListListEntry {
128+
val entry = ColorListListEntry(
129+
fieldName = fieldNameKey,
130+
value = value,
131+
defaultExpanded = isExpanded,
132+
tooltipSupplier = null,
133+
saveConsumer = saveConsumer,
134+
defaultValue = defaultValue,
135+
resetButtonKey = resetButtonKey,
136+
requiresRestart = isRequireRestart,
137+
deleteButtonEnabled = isDeleteButtonEnabled,
138+
insertInFront = isInsertInFront,
139+
)
140+
entry.isInsertButtonEnabled = isInsertButtonEnabled
141+
entry.cellErrorSupplier = cellErrorSupplier
142+
entry.setTooltipSupplier { tooltipSupplier.apply(entry.value) }
143+
entry.addTooltip = addTooltip
144+
entry.removeTooltip = removeTooltip
145+
errorSupplier?.let { entry.setErrorSupplier { it.apply(entry.value) } }
146+
return finishBuilding(entry)
147+
}
148+
}
149+
150+
fun ConfigEntryBuilder.startColorList(fieldNameKey: Component, value: List<Int>) =
151+
ColorListBuilder(resetButtonKey, fieldNameKey, value)

0 commit comments

Comments
 (0)