Skip to content

Commit

Permalink
Process VariantTooltip at build time
Browse files Browse the repository at this point in the history
Introduce `VariantTooltipProcessor`, which `LangTask`s use to process `VariantTooltip`s.

This allows the runtime implementation to be greatly simplified, it now only handles differences in line `count`.
  • Loading branch information
MattSturgeon committed Feb 8, 2024
1 parent 19c8191 commit 4d2f91c
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 36 deletions.
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/LangTask.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ abstract class LangTask : DefaultTask() {
private val gson = GsonBuilder().setPrettyPrinting().create()
private val localeRegex = "^[a-z]{2}-[A-Z]{2}$".toRegex()
private val processors = listOf<LangProcessor>(

VariantTooltipProcessor()
)

init {
Expand Down
37 changes: 37 additions & 0 deletions buildSrc/src/main/kotlin/VariantTooltipProcessor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
class VariantTooltipProcessor : LangProcessor {
private val variantRegex = "\\.@(?<variant>[^.]+)Tooltip(?<index>\\[\\d+])?${'$'}".toRegex()

override fun process(
modID: String,
variant: String,
translations: Map<String, String>,
fallback: Map<String, String>?
): Map<String, String> {
val map = translations.toMutableMap()
// Iterate over fallback values, to ensure variant-tooltips aren't accidentally overridden due to missing translations
fallback?.forEach { (key, _) ->
variantRegex.find(key)?.let { result ->
map.remove(key)
map.remove(baseKey(key, result))
}
}
// Then overwrite with actual values
translations.forEach { (key, value) ->
variantRegex.find(key)?.let { result ->
// This is normally handled by the first loop, but fallback is nullable...
map.remove(key)

// Add the variant translation
if (variant == result.groups["variant"]?.value?.lowercase()) {
map[baseKey(key, result)] = value
}
}
}
return map
}

private fun baseKey(variantKey: String, result: MatchResult): String {
val index = result.groups["index"]?.value ?: ""
return variantKey.replaceAfterLast('.', "@Tooltip${index}")
}
}
44 changes: 13 additions & 31 deletions common/src/main/java/net/xolt/freecam/config/ConfigExtensions.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,11 @@
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
import me.shedaniel.clothconfig2.gui.entries.TextListEntry;
import me.shedaniel.clothconfig2.gui.entries.TooltipListEntry;
import net.minecraft.locale.Language;
import net.minecraft.network.chat.Component;
import net.xolt.freecam.variant.api.BuildVariant;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.Contract;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.IntStream;

Expand Down Expand Up @@ -52,7 +47,7 @@ public static void init(GuiRegistry registry) {
}

/**
* Adds the correct tooltip for the current build environment to the specified GUIs that support tooltips.
* Adds the tooltip to the specified GUIs that {@link TooltipListEntry support tooltips}.
* <p>
* Note: as-per {@link ConfigEntry.Gui.Tooltip}, tooltips will not be added to {@link TextListEntry}s.
*
Expand All @@ -64,12 +59,11 @@ public static void init(GuiRegistry registry) {
@Contract(mutates = "param1")
private static void applyVariantTooltip(List<AbstractConfigListEntry> guis, List<VariantTooltip> tooltipVariants, String i18n) {
String variant = BuildVariant.getInstance().name();
List<String> variants = List.of(variant, "all");

// Number of tooltip lines defined for the current build variant (or "all")
// (throw if there isn't exactly one matching definition)
int count = tooltipVariants.stream()
.filter(entry -> variants.contains(entry.variant()))
.filter(entry -> Objects.equals(variant, entry.variant()))
.mapToInt(VariantTooltip::count)
.reduce((prev, next) -> {
throw new IllegalArgumentException("%s: Multiple variants matching \"%s\" declared on \"%s\".".formatted(VariantTooltip.class.getSimpleName(), variant, i18n));
Expand All @@ -82,59 +76,47 @@ private static void applyVariantTooltip(List<AbstractConfigListEntry> guis, List
.filter(gui -> !(gui instanceof TextListEntry))
.filter(TooltipListEntry.class::isInstance)
.map(gui -> (TooltipListEntry<?>) gui)
.forEach(gui -> gui.setTooltipSupplier(getVariantTooltip(variant, i18n, count)));
.forEach(gui -> gui.setTooltipSupplier(getTooltip(i18n, count)));
}

/**
* Generates a tooltip supplier for the given variant & base i18n key combination.
* Generates a tooltip supplier for the given base i18n key.
*
* @param variant the current build variant.
* @param i18n the config entry's translation key.
* @param count the number of lines in the tooltip.
* @return A tooltip supplier accepted by {@link TooltipListEntry#setTooltipSupplier(Supplier)}.
* @see TooltipListEntry
*/
private static Supplier<Optional<Component[]>> getVariantTooltip(String variant, String i18n, int count) {
private static Supplier<Optional<Component[]>> getTooltip(String i18n, int count) {
if (count == 0) {
return Optional::empty;
}

// We can cache the tooltip since language can't change while config GUI is open.
Optional<Component[]> tooltip;
if (count == 1) {
tooltip = Optional.of(new Component[] { getVariantTooltipLine(variant, i18n, -1) });
tooltip = Optional.of(new Component[] { getTooltipLine(i18n, -1) });
} else {
tooltip = Optional.of(IntStream.range(0, count)
.mapToObj(i -> getVariantTooltipLine(variant, i18n, i))
.mapToObj(i -> getTooltipLine(i18n, i))
.toArray(Component[]::new));
}
return () -> tooltip;
}

/**
* Generates a tooltip line for the given variant, base i18n key & line index combination.
* <p>
* Falls back to the default {@code @Tooltip} line if no key exists for the specified variant.
* Generates a tooltip line for the given base i18n key & line index combination.
*
* @param variant the current build variant.
* @param i18n the config entry's translation key.
* @param index the line's index (or {@code -1}).
* @return A line of {@link Component text} to be included in a wider tooltip.
* @see #getVariantTooltip(String, String, int)
* @see #getTooltip(String, int)
*/
private static Component getVariantTooltipLine(String variant, String i18n, int index) {
String key = "%s.@%sTooltip".formatted(i18n, StringUtils.capitalize(variant));
private static Component getTooltipLine(String i18n, int index) {
String key = "%s.@Tooltip".formatted(i18n);
if (index > -1) {
key += "[%d]".formatted(index);
}
// FIXME how will this behave for untranslated languages?
if (Language.getInstance().has(key)) {
return Component.translatable(key);
}
if (variant.isEmpty()) {
return Component.empty();
}
// Fallback to default "@Tooltip" translation
return getVariantTooltipLine("", i18n, index);
return Component.translatable(key);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public static class UtilityConfig {
@ConfigEntry.Gui.Tooltip
public boolean disableOnDamage = true;

@VariantTooltip(count = 2)
@ConfigEntry.Gui.Tooltip(count = 2)
public boolean freezePlayer = false;

@VariantTooltip(variant = "normal", count = 2)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
/**
* Applies a tooltip to list entries that support it, defined in your lang file.
* <p>
* Will try to use translations defined for the current build variant (e.g. {@code @ModrinthTooltip}), but will
* fall back to using the default {@code @Tooltip} translations if variant-specific ones are not defined.
* Should be used over {@link ConfigEntry.Gui.Tooltip} when {@link #count()} varies between variants.
* <p>
* Can be declared multiple times on the same field.
*
Expand All @@ -19,7 +18,7 @@
@Repeatable(VariantTooltip.List.class)
public @interface VariantTooltip {

String variant() default "all";
String variant();

int count() default 1;

Expand Down

0 comments on commit 4d2f91c

Please sign in to comment.