package net.minecraft.client.gui.components; import com.google.common.collect.ImmutableList; import java.util.Collection; import java.util.List; import java.util.function.BooleanSupplier; import java.util.function.Function; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.OptionInstance; import net.minecraft.client.gui.components.CycleButton.ValueListSupplier.1; import net.minecraft.client.gui.components.CycleButton.ValueListSupplier.2; import net.minecraft.client.gui.narration.NarratedElementType; import net.minecraft.client.gui.narration.NarrationElementOutput; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import net.minecraft.util.Mth; import org.jetbrains.annotations.Nullable; @Environment(EnvType.CLIENT) public class CycleButton extends AbstractButton { public static final BooleanSupplier DEFAULT_ALT_LIST_SELECTOR = Screen::hasAltDown; private static final List BOOLEAN_OPTIONS = ImmutableList.of(Boolean.TRUE, Boolean.FALSE); private final Component name; private int index; private T value; private final CycleButton.ValueListSupplier values; private final Function valueStringifier; private final Function, MutableComponent> narrationProvider; private final CycleButton.OnValueChange onValueChange; private final boolean displayOnlyValue; private final OptionInstance.TooltipSupplier tooltipSupplier; CycleButton( int x, int y, int width, int height, Component message, Component name, int index, T value, CycleButton.ValueListSupplier values, Function valueStringifier, Function, MutableComponent> narrationProvider, CycleButton.OnValueChange onValueChange, OptionInstance.TooltipSupplier tooltipSupplier, boolean displayOnlyValue ) { super(x, y, width, height, message); this.name = name; this.index = index; this.value = value; this.values = values; this.valueStringifier = valueStringifier; this.narrationProvider = narrationProvider; this.onValueChange = onValueChange; this.displayOnlyValue = displayOnlyValue; this.tooltipSupplier = tooltipSupplier; this.updateTooltip(); } private void updateTooltip() { this.setTooltip(this.tooltipSupplier.apply(this.value)); } @Override public void onPress() { if (Screen.hasShiftDown()) { this.cycleValue(-1); } else { this.cycleValue(1); } } private void cycleValue(int delta) { List list = this.values.getSelectedList(); this.index = Mth.positiveModulo(this.index + delta, list.size()); T object = (T)list.get(this.index); this.updateValue(object); this.onValueChange.onValueChange(this, object); } private T getCycledValue(int delta) { List list = this.values.getSelectedList(); return (T)list.get(Mth.positiveModulo(this.index + delta, list.size())); } @Override public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) { if (scrollY > 0.0) { this.cycleValue(-1); } else if (scrollY < 0.0) { this.cycleValue(1); } return true; } public void setValue(T value) { List list = this.values.getSelectedList(); int i = list.indexOf(value); if (i != -1) { this.index = i; } this.updateValue(value); } private void updateValue(T value) { Component component = this.createLabelForValue(value); this.setMessage(component); this.value = value; this.updateTooltip(); } private Component createLabelForValue(T value) { return (Component)(this.displayOnlyValue ? (Component)this.valueStringifier.apply(value) : this.createFullName(value)); } private MutableComponent createFullName(T value) { return CommonComponents.optionNameValue(this.name, (Component)this.valueStringifier.apply(value)); } public T getValue() { return this.value; } @Override protected MutableComponent createNarrationMessage() { return (MutableComponent)this.narrationProvider.apply(this); } @Override public void updateWidgetNarration(NarrationElementOutput narrationElementOutput) { narrationElementOutput.add(NarratedElementType.TITLE, this.createNarrationMessage()); if (this.active) { T object = this.getCycledValue(1); Component component = this.createLabelForValue(object); if (this.isFocused()) { narrationElementOutput.add(NarratedElementType.USAGE, Component.translatable("narration.cycle_button.usage.focused", component)); } else { narrationElementOutput.add(NarratedElementType.USAGE, Component.translatable("narration.cycle_button.usage.hovered", component)); } } } public MutableComponent createDefaultNarrationMessage() { return wrapDefaultNarrationMessage((Component)(this.displayOnlyValue ? this.createFullName(this.value) : this.getMessage())); } public static CycleButton.Builder builder(Function valueStringifier) { return new CycleButton.Builder<>(valueStringifier); } public static CycleButton.Builder booleanBuilder(Component componentOn, Component componentOff) { return new CycleButton.Builder(boolean_ -> boolean_ ? componentOn : componentOff).withValues(BOOLEAN_OPTIONS); } public static CycleButton.Builder onOffBuilder() { return new CycleButton.Builder(boolean_ -> boolean_ ? CommonComponents.OPTION_ON : CommonComponents.OPTION_OFF).withValues(BOOLEAN_OPTIONS); } public static CycleButton.Builder onOffBuilder(boolean initialValue) { return onOffBuilder().withInitialValue(initialValue); } @Environment(EnvType.CLIENT) public static class Builder { private int initialIndex; @Nullable private T initialValue; private final Function valueStringifier; private OptionInstance.TooltipSupplier tooltipSupplier = object -> null; private Function, MutableComponent> narrationProvider = CycleButton::createDefaultNarrationMessage; private CycleButton.ValueListSupplier values = CycleButton.ValueListSupplier.create(ImmutableList.of()); private boolean displayOnlyValue; public Builder(Function valueStringifier) { this.valueStringifier = valueStringifier; } public CycleButton.Builder withValues(Collection values) { return this.withValues(CycleButton.ValueListSupplier.create(values)); } @SafeVarargs public final CycleButton.Builder withValues(T... values) { return this.withValues(ImmutableList.copyOf(values)); } public CycleButton.Builder withValues(List defaultList, List selectedList) { return this.withValues(CycleButton.ValueListSupplier.create(CycleButton.DEFAULT_ALT_LIST_SELECTOR, defaultList, selectedList)); } public CycleButton.Builder withValues(BooleanSupplier altListSelector, List defaultList, List selectedList) { return this.withValues(CycleButton.ValueListSupplier.create(altListSelector, defaultList, selectedList)); } public CycleButton.Builder withValues(CycleButton.ValueListSupplier values) { this.values = values; return this; } public CycleButton.Builder withTooltip(OptionInstance.TooltipSupplier tooltipSupplier) { this.tooltipSupplier = tooltipSupplier; return this; } public CycleButton.Builder withInitialValue(T initialValue) { this.initialValue = initialValue; int i = this.values.getDefaultList().indexOf(initialValue); if (i != -1) { this.initialIndex = i; } return this; } public CycleButton.Builder withCustomNarration(Function, MutableComponent> narrationProvider) { this.narrationProvider = narrationProvider; return this; } public CycleButton.Builder displayOnlyValue() { this.displayOnlyValue = true; return this; } public CycleButton create(Component message, CycleButton.OnValueChange onValueChange) { return this.create(0, 0, 150, 20, message, onValueChange); } public CycleButton create(int x, int y, int width, int height, Component name) { return this.create(x, y, width, height, name, (cycleButton, object) -> {}); } public CycleButton create(int x, int y, int width, int height, Component name, CycleButton.OnValueChange onValueChange) { List list = this.values.getDefaultList(); if (list.isEmpty()) { throw new IllegalStateException("No values for cycle button"); } else { T object = (T)(this.initialValue != null ? this.initialValue : list.get(this.initialIndex)); Component component = (Component)this.valueStringifier.apply(object); Component component2 = (Component)(this.displayOnlyValue ? component : CommonComponents.optionNameValue(name, component)); return new CycleButton<>( x, y, width, height, component2, name, this.initialIndex, object, this.values, this.valueStringifier, this.narrationProvider, onValueChange, this.tooltipSupplier, this.displayOnlyValue ); } } } @Environment(EnvType.CLIENT) public interface OnValueChange { void onValueChange(CycleButton cycleButton, T object); } @Environment(EnvType.CLIENT) public interface ValueListSupplier { List getSelectedList(); List getDefaultList(); static CycleButton.ValueListSupplier create(Collection values) { List list = ImmutableList.copyOf(values); return new 1(list); } static CycleButton.ValueListSupplier create(BooleanSupplier altListSelector, List defaultList, List selectedList) { List list = ImmutableList.copyOf(defaultList); List list2 = ImmutableList.copyOf(selectedList); return new 2(altListSelector, list2, list); } } }