package dev.isxander.yacl.impl; import com.google.common.collect.ImmutableSet; import dev.isxander.yacl.api.*; import dev.isxander.yacl.impl.utils.YACLConstants; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.ComponentContents; import net.minecraft.network.chat.MutableComponent; import org.apache.commons.lang3.Validate; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; @ApiStatus.Internal public final class OptionImpl implements Option { private final Component name; private OptionDescription description; private final Controller controller; private final Binding binding; private boolean available; private final ImmutableSet flags; private final Class typeClass; private T pendingValue; private final List, T>> listeners; public OptionImpl( @NotNull Component name, @NotNull Function descriptionFunction, @NotNull Function, Controller> controlGetter, @NotNull Binding binding, boolean available, ImmutableSet flags, @NotNull Class typeClass, @NotNull Collection, T>> listeners ) { this.name = name; this.binding = binding; this.available = available; this.flags = flags; this.typeClass = typeClass; this.listeners = new ArrayList<>(listeners); this.controller = controlGetter.apply(this); addListener((opt, pending) -> description = descriptionFunction.apply(pending)); requestSet(binding().getValue()); } @Override public @NotNull Component name() { return name; } @Override public @NotNull OptionDescription description() { return this.description; } @Override public @NotNull Component tooltip() { return description.description(); } @Override public @NotNull Controller controller() { return controller; } @Override public @NotNull Binding binding() { return binding; } @Override public boolean available() { return available; } @Override public void setAvailable(boolean available) { this.available = available; } @Override public @NotNull Class typeClass() { return typeClass; } @Override public @NotNull ImmutableSet flags() { return flags; } @Override public boolean changed() { return !binding().getValue().equals(pendingValue); } @Override public @NotNull T pendingValue() { return pendingValue; } @Override public void requestSet(T value) { pendingValue = value; listeners.forEach(listener -> listener.accept(this, pendingValue)); } @Override public boolean applyValue() { if (changed()) { binding().setValue(pendingValue); return true; } return false; } @Override public void forgetPendingValue() { requestSet(binding().getValue()); } @Override public void requestSetDefault() { requestSet(binding().defaultValue()); } @Override public boolean isPendingValueDefault() { return binding().defaultValue().equals(pendingValue()); } @Override public void addListener(BiConsumer, T> changedListener) { this.listeners.add(changedListener); } @ApiStatus.Internal public static class BuilderImpl implements Builder { private Component name = Component.literal("Name not specified!").withStyle(ChatFormatting.RED); private Function descriptionFunction = null; private final List> tooltipGetters = new ArrayList<>(); private Function, Controller> controlGetter; private Binding binding; private boolean available = true; private boolean instant = false; private final Set flags = new HashSet<>(); private final Class typeClass; private final List, T>> listeners = new ArrayList<>(); public BuilderImpl(Class typeClass) { this.typeClass = typeClass; } @Override public Builder name(@NotNull Component name) { Validate.notNull(name, "`name` cannot be null"); this.name = name; return this; } @Override public Builder description(@NotNull OptionDescription description) { return description(opt -> description); } @Override public Builder description(@NotNull Function descriptionFunction) { this.descriptionFunction = descriptionFunction; return this; } @Override public Builder tooltip(@NotNull Function tooltipGetter) { Validate.notNull(tooltipGetter, "`tooltipGetter` cannot be null"); this.tooltipGetters.add(tooltipGetter); return this; } @Override @SafeVarargs @Deprecated public final Builder tooltip(@NotNull Function... tooltipGetter) { Validate.notNull(tooltipGetter, "`tooltipGetter` cannot be null"); this.tooltipGetters.addAll(List.of(tooltipGetter)); return this; } @Override public Builder tooltip(@NotNull Component... tooltips) { var tooltipFunctions = Arrays.stream(tooltips) .map(t -> (Function) opt -> t) .toList(); this.tooltipGetters.addAll(tooltipFunctions); return this; } @Override public Builder controller(@NotNull Function, Controller> control) { Validate.notNull(control, "`control` cannot be null"); this.controlGetter = control; return this; } @Override public Builder binding(@NotNull Binding binding) { Validate.notNull(binding, "`binding` cannot be null"); this.binding = binding; return this; } @Override public Builder binding(@NotNull T def, @NotNull Supplier<@NotNull T> getter, @NotNull Consumer<@NotNull T> setter) { Validate.notNull(def, "`def` must not be null"); Validate.notNull(getter, "`getter` must not be null"); Validate.notNull(setter, "`setter` must not be null"); this.binding = Binding.generic(def, getter, setter); return this; } @Override public Builder available(boolean available) { this.available = available; return this; } @Override public Builder flag(@NotNull OptionFlag... flag) { Validate.notNull(flag, "`flag` must not be null"); this.flags.addAll(Arrays.asList(flag)); return this; } @Override public Builder flags(@NotNull Collection flags) { Validate.notNull(flags, "`flags` must not be null"); this.flags.addAll(flags); return this; } @Override public Builder instant(boolean instant) { this.instant = instant; return this; } @Override public Builder listener(@NotNull BiConsumer, T> listener) { this.listeners.add(listener); return this; } @Override public Builder listeners(@NotNull Collection, T>> listeners) { this.listeners.addAll(listeners); return this; } @Override public Option build() { Validate.notNull(controlGetter, "`control` must not be null when building `Option`"); Validate.notNull(binding, "`binding` must not be null when building `Option`"); Validate.isTrue(!instant || flags.isEmpty(), "instant application does not support option flags"); if (descriptionFunction == null) { if (!tooltipGetters.isEmpty()) YACLConstants.LOGGER.warn("Using deprecated `tooltip` method in option '{}'. Use `description` instead.", name.getString()); Function concatenatedTooltipGetter = value -> { MutableComponent concatenatedTooltip = Component.empty(); boolean first = true; for (Function line : tooltipGetters) { Component lineComponent = line.apply(value); if (lineComponent.getContents() == ComponentContents.EMPTY) continue; if (!first) concatenatedTooltip.append("\n"); first = false; concatenatedTooltip.append(lineComponent); } return concatenatedTooltip; }; descriptionFunction = opt -> OptionDescription.createBuilder().description(concatenatedTooltipGetter.apply(opt)).build(); } return new OptionImpl<>(name, descriptionFunction, controlGetter, binding, available, ImmutableSet.copyOf(flags), typeClass, listeners); } } }