Skip to content

Commit 94bd2ff

Browse files
committed
split settings into tabs and move the api token off screen
1 parent efee6d4 commit 94bd2ff

2 files changed

Lines changed: 582 additions & 286 deletions

File tree

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package openshock.integrations.minecraft
2+
3+
import net.minecraft.client.gui.components.Button
4+
import net.minecraft.client.gui.components.EditBox
5+
import net.minecraft.client.gui.screens.Screen
6+
import net.minecraft.network.chat.Component
7+
import openshock.integrations.minecraft.config.AccountConfig
8+
import openshock.integrations.minecraft.platform.McCompat
9+
10+
/**
11+
* Where the API token is typed, on a screen of its own.
12+
*
13+
* The token is the one secret in the config - anyone holding it can shock whoever it belongs to -
14+
* and the settings screen is the thing most likely to be on camera while somebody is being talked
15+
* through the setup. So it is not on that screen at all: Setup says whether a token is set and
16+
* nothing more, and this is the only place one is ever entered.
17+
*
18+
* Which also means there is nothing to mask. The box starts **empty** rather than pre-filled with
19+
* what is already stored, so the stored token is never rendered anywhere, by anything. That is the
20+
* whole design, and it is why this replaced an attempt at drawing asterisks over YACL's own text
21+
* field: that meant fighting the widget's caret and scroll arithmetic on three different YACL
22+
* versions, for a weaker result than simply not drawing the thing.
23+
*
24+
* An empty box cannot be saved - Save stays greyed out until something is typed - so opening this
25+
* and pressing the obvious button cannot wipe a working token. Clearing is its own button.
26+
*
27+
* ### Versions
28+
*
29+
* Built out of [Button] and [EditBox] and nothing else, for the same reason [RemoteScreen] is
30+
* built out of buttons: both are spelled identically from 1.20.4 to 26.2, and a screen that only
31+
* adds widgets and never draws anything itself needs no per-version handling at all. Anything that
32+
* paints - a title, a label, a background - would have to follow Minecraft's rendering rewrites.
33+
* Hence the captions below being buttons nobody can press.
34+
*/
35+
class ApiTokenScreen(private val parent: Screen?) : Screen(Component.literal("API Token")) {
36+
37+
/** Longer than any token OpenShock issues, and far longer than [EditBox]'s default of 32. */
38+
private val maxLength = 256
39+
40+
override fun init() {
41+
val account = AccountConfig.HANDLER.instance()
42+
43+
val width = 260
44+
val height = 20
45+
val left = (this.width - width) / 2
46+
var top = this.height / 4
47+
48+
fun next(): Int {
49+
val y = top
50+
top += height + 6
51+
return y
52+
}
53+
54+
// What is already stored, said without saying it.
55+
caption(
56+
if (account.apiToken.isBlank()) "No API token set" else "An API token is already set",
57+
left, next(), width, height,
58+
)
59+
60+
caption("Paste a new one below to replace it", left, next(), width, height)
61+
62+
val box = EditBox(font, left, next(), width, height, Component.literal("API Token"))
63+
box.setMaxLength(maxLength)
64+
box.setHint(Component.literal("Paste your API token"))
65+
66+
val save = Button.builder(Component.literal("Save")) {
67+
// Trimmed because a token that arrives with a stray space or newline from a copy fails
68+
// authentication in a way that looks exactly like a wrong token.
69+
account.apiToken = box.value.trim()
70+
AccountConfig.HANDLER.save()
71+
72+
// Back to Setup, which rebuilds - and so notices the new token and goes and fetches the
73+
// shockers it can reach, which is the next thing anybody wants after setting one.
74+
back()
75+
}
76+
.bounds(left, next(), width, height)
77+
.build()
78+
79+
// Nothing typed is not a request to clear the token, it is somebody who opened this and
80+
// changed their mind. Clearing has its own button precisely so it cannot happen by accident.
81+
save.active = false
82+
box.setResponder { typed -> save.active = typed.isNotBlank() }
83+
84+
addRenderableWidget(box)
85+
addRenderableWidget(save)
86+
87+
addRenderableWidget(
88+
Button.builder(Component.literal("Clear token")) {
89+
account.apiToken = ""
90+
AccountConfig.HANDLER.save()
91+
back()
92+
}
93+
.bounds(left, next(), width, height)
94+
.build()
95+
.also { it.active = account.apiToken.isNotBlank() }
96+
)
97+
98+
addRenderableWidget(
99+
Button.builder(Component.literal("Cancel")) { back() }
100+
.bounds(left, next(), width, height)
101+
.build()
102+
)
103+
104+
// So the box takes typing straight away: this screen exists to be pasted into.
105+
setInitialFocus(box)
106+
}
107+
108+
/** Escape leaves the token alone, the same as Cancel. */
109+
override fun onClose() = back()
110+
111+
private fun back() = McCompat.setScreen(ConfigScreen.create(parent))
112+
113+
/** A line of text, as a button nobody can press - see the note on this class about drawing. */
114+
private fun caption(text: String, x: Int, y: Int, width: Int, height: Int) {
115+
addRenderableWidget(
116+
Button.builder(Component.literal(text)) { }
117+
.bounds(x, y, width, height)
118+
.build()
119+
.also { it.active = false }
120+
)
121+
}
122+
}

0 commit comments

Comments
 (0)