package net.minecraft.client.gui.components.toasts; import com.google.common.collect.Queues; import java.util.ArrayList; import java.util.BitSet; import java.util.Deque; import java.util.HashSet; import java.util.List; import java.util.Set; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.Util; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.resources.sounds.SimpleSoundInstance; import net.minecraft.sounds.SoundEvent; import net.minecraft.util.Mth; import org.apache.commons.lang3.mutable.MutableBoolean; import org.jetbrains.annotations.Nullable; @Environment(EnvType.CLIENT) public class ToastManager { private static final int SLOT_COUNT = 5; private static final int ALL_SLOTS_OCCUPIED = -1; final Minecraft minecraft; private final List> visibleToasts = new ArrayList(); private final BitSet occupiedSlots = new BitSet(5); private final Deque queued = Queues.newArrayDeque(); private final Set playedToastSounds = new HashSet(); public ToastManager(Minecraft minecraft) { this.minecraft = minecraft; } public void update() { MutableBoolean mutableBoolean = new MutableBoolean(false); this.visibleToasts.removeIf(toastInstance -> { Toast.Visibility visibility = toastInstance.visibility; toastInstance.update(); if (toastInstance.visibility != visibility && mutableBoolean.isFalse()) { mutableBoolean.setTrue(); toastInstance.visibility.playSound(this.minecraft.getSoundManager()); } if (toastInstance.hasFinishedRendering()) { this.occupiedSlots.clear(toastInstance.firstSlotIndex, toastInstance.firstSlotIndex + toastInstance.occupiedSlotCount); return true; } else { return false; } }); if (!this.queued.isEmpty() && this.freeSlotCount() > 0) { this.queued.removeIf(toast -> { int i = toast.occcupiedSlotCount(); int j = this.findFreeSlotsIndex(i); if (j == -1) { return false; } else { this.visibleToasts.add(new ToastManager.ToastInstance<>(toast, j, i)); this.occupiedSlots.set(j, j + i); SoundEvent soundEvent = toast.getSoundEvent(); if (soundEvent != null && this.playedToastSounds.add(soundEvent)) { this.minecraft.getSoundManager().play(SimpleSoundInstance.forUI(soundEvent, 1.0F, 1.0F)); } return true; } }); } this.playedToastSounds.clear(); } public void render(GuiGraphics guiGraphics) { if (!this.minecraft.options.hideGui) { int i = guiGraphics.guiWidth(); for (ToastManager.ToastInstance toastInstance : this.visibleToasts) { toastInstance.render(guiGraphics, i); } } } private int findFreeSlotsIndex(int slots) { if (this.freeSlotCount() >= slots) { int i = 0; for (int j = 0; j < 5; j++) { if (this.occupiedSlots.get(j)) { i = 0; } else if (++i == slots) { return j + 1 - i; } } } return -1; } private int freeSlotCount() { return 5 - this.occupiedSlots.cardinality(); } @Nullable public T getToast(Class toastClass, Object token) { for (ToastManager.ToastInstance toastInstance : this.visibleToasts) { if (toastInstance != null && toastClass.isAssignableFrom(toastInstance.getToast().getClass()) && toastInstance.getToast().getToken().equals(token)) { return (T)toastInstance.getToast(); } } for (Toast toast : this.queued) { if (toastClass.isAssignableFrom(toast.getClass()) && toast.getToken().equals(token)) { return (T)toast; } } return null; } public void clear() { this.occupiedSlots.clear(); this.visibleToasts.clear(); this.queued.clear(); } public void addToast(Toast toast) { this.queued.add(toast); } public Minecraft getMinecraft() { return this.minecraft; } public double getNotificationDisplayTimeMultiplier() { return this.minecraft.options.notificationDisplayTime().get(); } @Environment(EnvType.CLIENT) class ToastInstance { private static final long SLIDE_ANIMATION_DURATION_MS = 600L; private final T toast; final int firstSlotIndex; final int occupiedSlotCount; private long animationStartTime = -1L; private long becameFullyVisibleAt = -1L; Toast.Visibility visibility = Toast.Visibility.HIDE; private long fullyVisibleFor; private float visiblePortion; private boolean hasFinishedRendering; ToastInstance(final T toast, final int firstSlotIndex, final int occupiedSlotCount) { this.toast = toast; this.firstSlotIndex = firstSlotIndex; this.occupiedSlotCount = occupiedSlotCount; } public T getToast() { return this.toast; } public boolean hasFinishedRendering() { return this.hasFinishedRendering; } private void calculateVisiblePortion(long visibilityTime) { float f = Mth.clamp((float)(visibilityTime - this.animationStartTime) / 600.0F, 0.0F, 1.0F); f *= f; if (this.visibility == Toast.Visibility.HIDE) { this.visiblePortion = 1.0F - f; } else { this.visiblePortion = f; } } public void update() { long l = Util.getMillis(); if (this.animationStartTime == -1L) { this.animationStartTime = l; this.visibility = Toast.Visibility.SHOW; } if (this.visibility == Toast.Visibility.SHOW && l - this.animationStartTime <= 600L) { this.becameFullyVisibleAt = l; } this.fullyVisibleFor = l - this.becameFullyVisibleAt; this.calculateVisiblePortion(l); this.toast.update(ToastManager.this, this.fullyVisibleFor); Toast.Visibility visibility = this.toast.getWantedVisibility(); if (visibility != this.visibility) { this.animationStartTime = l - (int)((1.0F - this.visiblePortion) * 600.0F); this.visibility = visibility; } this.hasFinishedRendering = this.visibility == Toast.Visibility.HIDE && l - this.animationStartTime > 600L; } public void render(GuiGraphics guiGraphics, int guiWidth) { guiGraphics.pose().pushPose(); guiGraphics.pose().translate(guiWidth - this.toast.width() * this.visiblePortion, (float)(this.firstSlotIndex * 32), 800.0F); this.toast.render(guiGraphics, ToastManager.this.minecraft.font, this.fullyVisibleFor); guiGraphics.pose().popPose(); } } }