Skip to content

Commit 0f9e496

Browse files
authored
feat(Theme): settings (#6808)
1 parent 317157d commit 0f9e496

File tree

15 files changed

+239
-150
lines changed

15 files changed

+239
-150
lines changed

src-theme/public/metadata.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,6 @@
5353
"name": "background",
5454
"types": ["frag", "png"]
5555
}
56-
]
56+
],
57+
"values": []
5758
}

src-theme/src/integration/rest.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import type {
2222
RegistryItem,
2323
Server,
2424
Session,
25+
Theme,
2526
VirtualScreen,
2627
World
2728
} from "./types";
@@ -592,6 +593,17 @@ export async function getGameWindow(): Promise<GameWindow> {
592593
return data;
593594
}
594595

596+
/**
597+
* @param id Use the ID from [getMetadata].
598+
*/
599+
export async function getTheme(id: string): Promise<Theme> {
600+
const response = await fetch(`${API_BASE}/client/theme/${id}`);
601+
return await response.json();
602+
}
603+
604+
/**
605+
* @param id Use the ID from [getMetadata].
606+
*/
595607
export async function getComponents(id: string): Promise<Component[]> {
596608
const response = await fetch(`${API_BASE}/client/components/${id}`);
597609
return await response.json();

src-theme/src/integration/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,12 @@ export interface GameWindow {
354354
guiScale: number;
355355
}
356356

357+
export interface Theme {
358+
name: string;
359+
id: string;
360+
settings: { [name: string]: any };
361+
}
362+
357363
export interface Component {
358364
name: string;
359365
id: string;

src/main/kotlin/net/ccbluex/liquidbounce/config/gson/GsonInstance.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import net.ccbluex.liquidbounce.config.gson.stategies.ProtocolExcludeStrategy
3232
import net.ccbluex.liquidbounce.config.types.NamedChoice
3333
import net.ccbluex.liquidbounce.config.types.nesting.ChoiceConfigurable
3434
import net.ccbluex.liquidbounce.config.types.nesting.Configurable
35+
import net.ccbluex.liquidbounce.integration.theme.Theme
3536
import net.ccbluex.liquidbounce.integration.theme.component.Component
3637
import net.ccbluex.liquidbounce.render.engine.type.Color4b
3738
import net.ccbluex.liquidbounce.utils.input.InputBind
@@ -95,6 +96,7 @@ internal val accessibleInteropGson: Gson = GsonBuilder()
9596
.addSerializationExclusionStrategy(ProtocolExcludeStrategy)
9697
.registerCommonTypeAdapters()
9798
.registerTypeHierarchyAdapter(Configurable::class.javaObjectType, ConfigurableSerializer.INTEROP_SERIALIZER)
99+
.registerTypeHierarchyAdapter(Theme::class.javaObjectType, ReadOnlyThemeSerializer)
98100
.registerTypeHierarchyAdapter(Component::class.javaObjectType, ReadOnlyComponentSerializer)
99101
.registerTypeHierarchyAdapter(Alignment::class.javaObjectType, AlignmentAdapter)
100102
.create()

src/main/kotlin/net/ccbluex/liquidbounce/config/gson/serializer/ConfigurableSerializer.kt

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ package net.ccbluex.liquidbounce.config.gson.serializer
2222
import com.google.gson.JsonObject
2323
import com.google.gson.JsonSerializationContext
2424
import com.google.gson.JsonSerializer
25-
import net.ccbluex.liquidbounce.config.types.nesting.Configurable
2625
import net.ccbluex.liquidbounce.config.types.Value
26+
import net.ccbluex.liquidbounce.config.types.nesting.Configurable
2727
import net.ccbluex.liquidbounce.features.module.Category
2828
import net.ccbluex.liquidbounce.features.module.ClientModule
29+
import net.ccbluex.liquidbounce.utils.client.toLowerCamelCase
30+
import net.ccbluex.liquidbounce.utils.render.Alignment
2931
import java.lang.reflect.Type
3032

3133
class ConfigurableSerializer(
@@ -55,6 +57,25 @@ class ConfigurableSerializer(
5557
withValueType = false, includePrivate = false, includeNotAnOption = true
5658
)
5759

60+
/**
61+
* Serialize a [Configurable] to a read-only [JsonObject]
62+
*
63+
* Used for interop communication by [ReadOnlyComponentSerializer]
64+
* and [ReadOnlyThemeSerializer].
65+
*/
66+
fun serializeReadOnly(
67+
configurable: Configurable,
68+
context: JsonSerializationContext
69+
): JsonObject = JsonObject().apply {
70+
for (v in configurable.inner) {
71+
add(v.name.toLowerCamelCase(), when (v) {
72+
is Alignment -> context.serialize(v, Alignment::class.java)
73+
is Configurable -> serializeReadOnly(v, context)
74+
else -> context.serialize(v.inner)
75+
})
76+
}
77+
}
78+
5879
}
5980

6081
override fun serialize(

src/main/kotlin/net/ccbluex/liquidbounce/config/gson/serializer/ReadOnlyComponentSerializer.kt

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,8 @@ package net.ccbluex.liquidbounce.config.gson.serializer
2424
import com.google.gson.JsonObject
2525
import com.google.gson.JsonSerializationContext
2626
import com.google.gson.JsonSerializer
27-
import net.ccbluex.liquidbounce.config.types.nesting.Configurable
27+
import net.ccbluex.liquidbounce.config.gson.serializer.ConfigurableSerializer.Companion.serializeReadOnly
2828
import net.ccbluex.liquidbounce.integration.theme.component.Component
29-
import net.ccbluex.liquidbounce.utils.client.toLowerCamelCase
30-
import net.ccbluex.liquidbounce.utils.render.Alignment
3129
import java.lang.reflect.Type
3230

3331
object ReadOnlyComponentSerializer : JsonSerializer<Component> {
@@ -42,17 +40,6 @@ object ReadOnlyComponentSerializer : JsonSerializer<Component> {
4240
add("settings", serializeReadOnly(src, context))
4341
}
4442

45-
private fun serializeReadOnly(
46-
configurable: Configurable,
47-
context: JsonSerializationContext
48-
): JsonObject = JsonObject().apply {
49-
for (v in configurable.inner) {
50-
add(v.name.toLowerCamelCase(), when (v) {
51-
is Alignment -> context.serialize(v, Alignment::class.java)
52-
is Configurable -> serializeReadOnly(v, context)
53-
else -> context.serialize(v.inner)
54-
})
55-
}
56-
}
43+
5744

5845
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce)
3+
*
4+
* Copyright (c) 2015 - 2025 CCBlueX
5+
*
6+
* LiquidBounce is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* LiquidBounce is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with LiquidBounce. If not, see <https://www.gnu.org/licenses/>.
18+
*
19+
*
20+
*/
21+
22+
package net.ccbluex.liquidbounce.config.gson.serializer
23+
24+
import com.google.gson.JsonObject
25+
import com.google.gson.JsonSerializationContext
26+
import com.google.gson.JsonSerializer
27+
import net.ccbluex.liquidbounce.config.gson.serializer.ConfigurableSerializer.Companion.serializeReadOnly
28+
import net.ccbluex.liquidbounce.integration.theme.Theme
29+
import java.lang.reflect.Type
30+
31+
object ReadOnlyThemeSerializer : JsonSerializer<Theme> {
32+
33+
override fun serialize(
34+
src: Theme,
35+
typeOfSrc: Type,
36+
context: JsonSerializationContext
37+
) = JsonObject().apply {
38+
addProperty("name", src.metadata.name)
39+
addProperty("id", src.metadata.id)
40+
add("settings", serializeReadOnly(src.settings, context))
41+
}
42+
43+
}

src/main/kotlin/net/ccbluex/liquidbounce/config/types/nesting/Configurable.kt

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package net.ccbluex.liquidbounce.config.types.nesting
2020

21+
import com.google.gson.JsonObject
2122
import net.ccbluex.liquidbounce.config.types.*
2223
import net.ccbluex.liquidbounce.config.types.CurveValue.Axis
2324
import net.ccbluex.liquidbounce.event.EventListener
@@ -404,4 +405,108 @@ open class Configurable(
404405

405406
fun value(value: Value<*>) = value.apply { this@Configurable.inner.add(this) }
406407

408+
/**
409+
* Assigns the value of the settings to the component
410+
*
411+
* A component can have dynamic settings which can be assigned through the JSON file
412+
* These have to be interpreted and assigned to the configurable
413+
*
414+
* An example:
415+
* {
416+
* "type": "INT",
417+
* "name": "Size",
418+
* "value": 14,
419+
* "range": {
420+
* "min": 1,
421+
* "max": 100
422+
* },
423+
* "suffix": "px"
424+
* }
425+
*
426+
* TODO: Replace with proper deserialization
427+
*
428+
* @param valueObject JsonObject
429+
*/
430+
@Suppress("LongMethod")
431+
fun json(valueObject: JsonObject) {
432+
val type = valueObject["type"].asString
433+
val name = valueObject["name"].asString
434+
435+
// todo: replace this with serious deserialization
436+
when (type) {
437+
"BOOLEAN" -> {
438+
val value = valueObject["value"].asBoolean
439+
boolean(name, value)
440+
}
441+
442+
"INT" -> {
443+
val value = valueObject["value"].asInt
444+
val min = valueObject["range"].asJsonObject["min"].asInt
445+
val max = valueObject["range"].asJsonObject["max"].asInt
446+
val suffix = valueObject["suffix"]?.asString ?: ""
447+
int(name, value, min..max, suffix)
448+
}
449+
450+
"INT_RANGE" -> {
451+
val valueMin = valueObject["value"].asJsonObject["min"].asInt
452+
val valueMax = valueObject["value"].asJsonObject["max"].asInt
453+
val min = valueObject["range"].asJsonObject["min"].asInt
454+
val max = valueObject["range"].asJsonObject["max"].asInt
455+
val suffix = valueObject["suffix"]?.asString ?: ""
456+
intRange(name, valueMin..valueMax, min..max, suffix)
457+
}
458+
459+
"FLOAT" -> {
460+
val value = valueObject["value"].asFloat
461+
val min = valueObject["range"].asJsonObject["min"].asFloat
462+
val max = valueObject["range"].asJsonObject["max"].asFloat
463+
val suffix = valueObject["suffix"]?.asString ?: ""
464+
float(name, value, min..max, suffix)
465+
}
466+
467+
"FLOAT_RANGE" -> {
468+
val valueMin = valueObject["value"].asJsonObject["min"].asFloat
469+
val valueMax = valueObject["value"].asJsonObject["max"].asFloat
470+
val min = valueObject["range"].asJsonObject["min"].asFloat
471+
val max = valueObject["range"].asJsonObject["max"].asFloat
472+
val suffix = valueObject["suffix"]?.asString ?: ""
473+
floatRange(name, valueMin..valueMax, min..max, suffix)
474+
}
475+
476+
"TEXT" -> {
477+
val value = valueObject["value"].asString
478+
text(name, value)
479+
}
480+
481+
"COLOR" -> {
482+
val value = valueObject["value"].asInt
483+
color(name, Color4b(value, hasAlpha = true))
484+
}
485+
486+
"CONFIGURABLE" -> {
487+
val subConfigurable = Configurable(name)
488+
val values = valueObject["values"].asJsonArray
489+
for (value in values) {
490+
subConfigurable.json(value.asJsonObject)
491+
}
492+
tree(subConfigurable)
493+
}
494+
// same as configurable but it is [ToggleableConfigurable]
495+
"TOGGLEABLE" -> {
496+
val value = valueObject["value"].asBoolean
497+
// Parent is NULL in that case because we are not dealing with Listenable anyway and only use it
498+
// as toggleable Configurable
499+
val subConfigurable = object : ToggleableConfigurable(null, name, value) {}
500+
val settings = valueObject["values"].asJsonArray
501+
for (setting in settings) {
502+
subConfigurable.json(setting.asJsonObject)
503+
}
504+
tree(subConfigurable)
505+
}
506+
507+
else -> error("Unsupported type: $type")
508+
}
509+
}
510+
511+
407512
}

src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/ModuleHud.kt

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
*/
1919
package net.ccbluex.liquidbounce.features.module.modules.render
2020

21-
import net.ccbluex.liquidbounce.config.types.Value
2221
import net.ccbluex.liquidbounce.config.types.nesting.Configurable
2322
import net.ccbluex.liquidbounce.event.EventManager
2423
import net.ccbluex.liquidbounce.event.events.BrowserReadyEvent
@@ -30,13 +29,12 @@ import net.ccbluex.liquidbounce.features.misc.HideAppearance.isDestructed
3029
import net.ccbluex.liquidbounce.features.misc.HideAppearance.isHidingNow
3130
import net.ccbluex.liquidbounce.features.module.Category
3231
import net.ccbluex.liquidbounce.features.module.ClientModule
33-
import net.ccbluex.liquidbounce.features.module.modules.render.ModuleHud.components
32+
import net.ccbluex.liquidbounce.features.module.modules.render.ModuleHud.themes
3433
import net.ccbluex.liquidbounce.integration.VirtualScreenType
3534
import net.ccbluex.liquidbounce.integration.backend.browser.Browser
3635
import net.ccbluex.liquidbounce.integration.backend.browser.BrowserSettings
3736
import net.ccbluex.liquidbounce.integration.backend.browser.GlobalBrowserSettings
3837
import net.ccbluex.liquidbounce.integration.theme.ThemeManager
39-
import net.ccbluex.liquidbounce.integration.theme.ThemeManager.themes
4038
import net.ccbluex.liquidbounce.integration.theme.component.components.minimap.MinimapComponent
4139
import net.ccbluex.liquidbounce.utils.client.chat
4240
import net.ccbluex.liquidbounce.utils.client.inGame
@@ -77,28 +75,22 @@ object ModuleHud : ClientModule("HUD", Category.RENDER, state = true, hide = tru
7775

7876
private var browserSettings: BrowserSettings? = null
7977

80-
val nativeComponents = listOf(MinimapComponent)
78+
val themes = tree(Configurable("Themes"))
8179

82-
val components = tree(Configurable("Components")).apply {
83-
nativeComponents.forEach(this::tree)
80+
val components = tree(Configurable("AdditionalComponents")).apply {
81+
tree(MinimapComponent)
8482
}
8583

8684
/**
87-
* Updates [components] content
85+
* Updates [themes] content
8886
*/
89-
fun updateComponents() {
90-
components.inner.clear()
91-
nativeComponents.forEach { component ->
92-
components.tree(component)
87+
fun updateThemes() {
88+
themes.inner.clear()
89+
for (theme in ThemeManager.themes) {
90+
themes.tree(theme.settings)
9391
}
94-
95-
for (theme in themes) {
96-
val themeConfigurable = Configurable(theme.metadata.id, theme.components as MutableList<Value<*>>)
97-
components.tree(themeConfigurable)
98-
}
99-
100-
components.initConfigurable()
101-
components.walkKeyPath()
92+
themes.initConfigurable()
93+
themes.walkKeyPath()
10294
}
10395

10496
override fun onEnabled() {

src/main/kotlin/net/ccbluex/liquidbounce/integration/interop/protocol/rest/v1/InteropFunctionRegistry.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ internal fun registerInteropFunctions(node: Node) = node.withPath("/api/v1/clien
4747
delete("/localStorage", ::deleteLocalStorage)
4848

4949
// Theme Functions
50-
get("/theme", ::getThemeInfo)
50+
get("/theme", ::getTheme) // returns current theme
51+
get("/theme/:id", ::getTheme)
5152
get("/shader", ::getToggleShaderInfo)
5253
post("/shader", ::postToggleShader)
5354

0 commit comments

Comments
 (0)