package io.gitlab.jfronny.libjf.config.impl.ui.tiny; import io.gitlab.jfronny.commons.throwable.Try; import io.gitlab.jfronny.libjf.LibJf; import io.gitlab.jfronny.libjf.config.api.v1.ConfigCategory; import io.gitlab.jfronny.libjf.config.api.v1.dsl.CategoryBuilder; import io.gitlab.jfronny.libjf.config.impl.entrypoint.JfConfigSafe; import io.gitlab.jfronny.libjf.config.impl.ui.tiny.entry.EntryListWidget; import io.gitlab.jfronny.libjf.config.impl.ui.tiny.entry.WidgetState; import io.gitlab.jfronny.libjf.config.impl.ui.tiny.presets.PresetsScreen; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.ScreenRect; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.tab.TabManager; import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.gui.widget.TabNavigationWidget; import net.minecraft.client.resource.language.I18n; import net.minecraft.screen.ScreenTexts; import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.math.MathHelper; import java.util.*; @Environment(EnvType.CLIENT) public class TinyConfigScreen extends Screen { public static Text getTitle(String categoryPath) { final String titlePath = categoryPath + "title"; if (JfConfigSafe.TRANSLATION_SUPPLIER.apply(titlePath) != null) { return Text.translatable(titlePath); } final String alternatePath = categoryPath.isEmpty() ? "" : categoryPath.substring(0, categoryPath.length() - 1); if (JfConfigSafe.TRANSLATION_SUPPLIER.apply(alternatePath) != null) { return Text.translatable(alternatePath); } return Text.translatable(titlePath); } private final Screen parent; private final ConfigCategory config; public final List> widgets; // Filled in from TinyConfigTab private final Placeholder placeholder; private final TabManager tabManager = new TabManager(a -> selectTab(((TinyConfigTabWrapper)a).getTab()), a -> {}); private List tabs; private final boolean considerTabs; public ButtonWidget done; private boolean reload = false; public TinyConfigScreen(ConfigCategory config, Screen parent) { super(getTitle(config.getTranslationPrefix())); this.parent = parent; this.config = config; this.widgets = new LinkedList<>(); this.placeholder = new Placeholder<>(null); this.considerTabs = config.getEntries().isEmpty() && config.getPresets().keySet().stream().allMatch(s -> s.equals(CategoryBuilder.CONFIG_PRESET_DEFAULT)) && config.getReferencedConfigs().isEmpty(); } @Override public void tick() { super.tick(); tabManager.tick(); } @Override protected void init() { super.init(); if (!reload) { this.done = ButtonWidget.builder(ScreenTexts.DONE, button -> { for (WidgetState state : widgets) { Try.orElse(state::writeToEntry, e -> LibJf.LOGGER.error("Could not write config data to class", e)); } config.getRoot().write(); Objects.requireNonNull(client).setScreen(parent); }) .dimensions(this.width / 2 + 4, this.height - 28, 150, 20) .build(); } else done.setPosition(width / 2 + 4, height - 28); updateTabs(); TabNavigationWidget tabNavigation = TabNavigationWidget.builder(tabManager, this.width) .tabs(tabs.toArray(TinyConfigTab[]::new)) .build(); if (tabs.size() > 1) this.addDrawableChild(tabNavigation); tabNavigation.selectTab(0, false); tabNavigation.init(); this.addDrawableChild(ButtonWidget.builder(ScreenTexts.CANCEL, button -> Objects.requireNonNull(client).setScreen(parent)) .dimensions(this.width / 2 - 154, this.height - 28, 150, 20) .build()); this.addDrawableChild(done); if (tabs.size() == 1 && !config.getPresets().isEmpty()) { this.addDrawableChild(ButtonWidget.builder(Text.translatable("libjf-config-v1.presets"), button -> Objects.requireNonNull(client).setScreen(new PresetsScreen(this, config, this::afterSelectPreset))) .dimensions(4, 6, 80, 20) .build()); } this.addSelectableChild(this.placeholder); // Sizing is also done in TinyConfigTab. Keep these in sync! tabManager.setTabArea(new ScreenRect(0, 32, width, height - 68)); reload = true; } private boolean wasTabs = false; private void updateTabs() { boolean useTabs = considerTabs && !tabsWouldOverflow(config.getCategories().values()); if (reload) { if (!considerTabs) return; if (wasTabs == useTabs) return; } wasTabs = useTabs; tabs = !useTabs ? List.of() : config.getCategories() .values() .stream() .map(c -> new TinyConfigTab(this, c, textRenderer, false)) .toList(); if (tabs.isEmpty()) tabs = List.of(new TinyConfigTab(this, config, textRenderer, true)); } private boolean tabsWouldOverflow(Collection categories) { int tabNavWidth = this.width; int headerWidth = Math.min(400, tabNavWidth) - 28; int singleHeaderWidth = MathHelper.roundUpToMultiple(headerWidth / categories.size(), 2); int singleTextWidth = singleHeaderWidth - 2; TextRenderer tr = MinecraftClient.getInstance().textRenderer; for (ConfigCategory category : categories) { if (tr.getWidth(getTitle(category.getTranslationPrefix())) > singleTextWidth) return true; } return false; } public void afterSelectPreset() { for (WidgetState widget : widgets) { widget.updateCache(); } } private void selectTab(TinyConfigTab tab) { placeholder.setChild(tab.getList()); } @Override public void render(DrawContext context, int mouseX, int mouseY, float delta) { this.renderBackground(context); this.placeholder.render(context, mouseX, mouseY, delta); if (tabs.size() == 1) context.drawCenteredTextWithShadow(textRenderer, title, width / 2, 16 - textRenderer.fontHeight, 0xFFFFFF); Optional hovered = placeholder.getChild().getHoveredEntryTitle(mouseY); if (hovered.isPresent()) { for (WidgetState info : widgets) { Text text = hovered.get(); MutableText name = Text.translatable(info.id); boolean showTooltip = text.equals(name); String tooltipKey = info.id + ".tooltip"; if (showTooltip && info.error != null) { showTooltip = false; context.drawTooltip(textRenderer, info.error, mouseX, mouseY); } if (showTooltip && I18n.hasTranslation(tooltipKey)) { showTooltip = false; List tooltip = new ArrayList<>(); for (String str : I18n.translate(tooltipKey).split("\n")) tooltip.add(Text.literal(str)); context.drawTooltip(textRenderer, tooltip, mouseX, mouseY); } } } super.render(context, mouseX, mouseY, delta); } @Override public void close() { Objects.requireNonNull(client).setScreen(parent); } public MinecraftClient getClient() { return Objects.requireNonNull(client); } }