package net.minecraft.client.gui.components; import java.time.Duration; import java.util.function.Consumer; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.Util; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.ComponentPath; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.layouts.LayoutElement; import net.minecraft.client.gui.narration.NarratableEntry; import net.minecraft.client.gui.narration.NarratedElementType; import net.minecraft.client.gui.narration.NarrationElementOutput; import net.minecraft.client.gui.navigation.FocusNavigationEvent; import net.minecraft.client.gui.navigation.ScreenRectangle; import net.minecraft.client.resources.sounds.SimpleSoundInstance; import net.minecraft.client.sounds.SoundManager; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import net.minecraft.sounds.SoundEvents; import net.minecraft.util.Mth; import org.jetbrains.annotations.Nullable; @Environment(EnvType.CLIENT) public abstract class AbstractWidget implements Renderable, GuiEventListener, LayoutElement, NarratableEntry { private static final double PERIOD_PER_SCROLLED_PIXEL = 0.5; private static final double MIN_SCROLL_PERIOD = 3.0; protected int width; protected int height; private int x; private int y; private Component message; protected boolean isHovered; public boolean active = true; public boolean visible = true; protected float alpha = 1.0F; private int tabOrderGroup; private boolean focused; private final WidgetTooltipHolder tooltip = new WidgetTooltipHolder(); public AbstractWidget(int x, int y, int width, int height, Component message) { this.x = x; this.y = y; this.width = width; this.height = height; this.message = message; } @Override public int getHeight() { return this.height; } @Override public final void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { if (this.visible) { this.isHovered = guiGraphics.containsPointInScissor(mouseX, mouseY) && mouseX >= this.getX() && mouseY >= this.getY() && mouseX < this.getX() + this.width && mouseY < this.getY() + this.height; this.renderWidget(guiGraphics, mouseX, mouseY, partialTick); this.tooltip.refreshTooltipForNextRenderPass(this.isHovered(), this.isFocused(), this.getRectangle()); } } public void setTooltip(@Nullable Tooltip tooltip) { this.tooltip.set(tooltip); } @Nullable public Tooltip getTooltip() { return this.tooltip.get(); } public void setTooltipDelay(Duration tooltipDelay) { this.tooltip.setDelay(tooltipDelay); } protected MutableComponent createNarrationMessage() { return wrapDefaultNarrationMessage(this.getMessage()); } public static MutableComponent wrapDefaultNarrationMessage(Component message) { return Component.translatable("gui.narrate.button", message); } protected abstract void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick); protected static void renderScrollingString(GuiGraphics guiGraphics, Font font, Component text, int minX, int minY, int maxX, int maxY, int color) { renderScrollingString(guiGraphics, font, text, (minX + maxX) / 2, minX, minY, maxX, maxY, color); } protected static void renderScrollingString(GuiGraphics guiGraphics, Font font, Component text, int centerX, int minX, int minY, int maxX, int maxY, int color) { int i = font.width(text); int j = (minY + maxY - 9) / 2 + 1; int k = maxX - minX; if (i > k) { int l = i - k; double d = Util.getMillis() / 1000.0; double e = Math.max(l * 0.5, 3.0); double f = Math.sin((Math.PI / 2) * Math.cos((Math.PI * 2) * d / e)) / 2.0 + 0.5; double g = Mth.lerp(f, 0.0, (double)l); guiGraphics.enableScissor(minX, minY, maxX, maxY); guiGraphics.drawString(font, text, minX - (int)g, j, color); guiGraphics.disableScissor(); } else { int l = Mth.clamp(centerX, minX + i / 2, maxX - i / 2); guiGraphics.drawCenteredString(font, text, l, j, color); } } protected void renderScrollingString(GuiGraphics guiGraphics, Font font, int width, int color) { int i = this.getX() + width; int j = this.getX() + this.getWidth() - width; renderScrollingString(guiGraphics, font, this.getMessage(), i, this.getY(), j, this.getY() + this.getHeight(), color); } public void onClick(double mouseX, double mouseY) { } public void onRelease(double mouseX, double mouseY) { } protected void onDrag(double mouseX, double mouseY, double dragX, double dragY) { } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { if (this.active && this.visible) { if (this.isValidClickButton(button)) { boolean bl = this.isMouseOver(mouseX, mouseY); if (bl) { this.playDownSound(Minecraft.getInstance().getSoundManager()); this.onClick(mouseX, mouseY); return true; } } return false; } else { return false; } } @Override public boolean mouseReleased(double mouseX, double mouseY, int button) { if (this.isValidClickButton(button)) { this.onRelease(mouseX, mouseY); return true; } else { return false; } } protected boolean isValidClickButton(int button) { return button == 0; } @Override public boolean mouseDragged(double mouseX, double mouseY, int button, double dragX, double dragY) { if (this.isValidClickButton(button)) { this.onDrag(mouseX, mouseY, dragX, dragY); return true; } else { return false; } } @Nullable @Override public ComponentPath nextFocusPath(FocusNavigationEvent event) { if (!this.active || !this.visible) { return null; } else { return !this.isFocused() ? ComponentPath.leaf(this) : null; } } @Override public boolean isMouseOver(double mouseX, double mouseY) { return this.active && this.visible && mouseX >= this.getX() && mouseY >= this.getY() && mouseX < this.getRight() && mouseY < this.getBottom(); } public void playDownSound(SoundManager handler) { playButtonClickSound(handler); } public static void playButtonClickSound(SoundManager soundManager) { soundManager.play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F)); } @Override public int getWidth() { return this.width; } public void setWidth(int width) { this.width = width; } public void setHeight(int height) { this.height = height; } public void setAlpha(float alpha) { this.alpha = alpha; } public void setMessage(Component message) { this.message = message; } public Component getMessage() { return this.message; } @Override public boolean isFocused() { return this.focused; } public boolean isHovered() { return this.isHovered; } public boolean isHoveredOrFocused() { return this.isHovered() || this.isFocused(); } @Override public boolean isActive() { return this.visible && this.active; } @Override public void setFocused(boolean focused) { this.focused = focused; } @Override public NarratableEntry.NarrationPriority narrationPriority() { if (this.isFocused()) { return NarratableEntry.NarrationPriority.FOCUSED; } else { return this.isHovered ? NarratableEntry.NarrationPriority.HOVERED : NarratableEntry.NarrationPriority.NONE; } } @Override public final void updateNarration(NarrationElementOutput narrationElementOutput) { this.updateWidgetNarration(narrationElementOutput); this.tooltip.updateNarration(narrationElementOutput); } protected abstract void updateWidgetNarration(NarrationElementOutput narrationElementOutput); protected void defaultButtonNarrationText(NarrationElementOutput narrationElementOutput) { narrationElementOutput.add(NarratedElementType.TITLE, this.createNarrationMessage()); if (this.active) { if (this.isFocused()) { narrationElementOutput.add(NarratedElementType.USAGE, Component.translatable("narration.button.usage.focused")); } else { narrationElementOutput.add(NarratedElementType.USAGE, Component.translatable("narration.button.usage.hovered")); } } } @Override public int getX() { return this.x; } @Override public void setX(int x) { this.x = x; } @Override public int getY() { return this.y; } @Override public void setY(int y) { this.y = y; } public int getRight() { return this.getX() + this.getWidth(); } public int getBottom() { return this.getY() + this.getHeight(); } @Override public void visitWidgets(Consumer consumer) { consumer.accept(this); } public void setSize(int width, int height) { this.width = width; this.height = height; } @Override public ScreenRectangle getRectangle() { return LayoutElement.super.getRectangle(); } public void setRectangle(int width, int height, int x, int y) { this.setSize(width, height); this.setPosition(x, y); } @Override public int getTabOrderGroup() { return this.tabOrderGroup; } public void setTabOrderGroup(int tabOrderGroup) { this.tabOrderGroup = tabOrderGroup; } }