package org.saturnclient.ui.screens; import java.util.List; import org.saturnclient.client.ServiceClient; import org.saturnclient.common.provider.Providers; import org.saturnclient.common.ref.asset.IdentifierRef; import org.saturnclient.config.AnimationConfig; import org.saturnclient.config.Config; import org.saturnclient.config.property.FloatProperty; import org.saturnclient.config.Theme; import org.saturnclient.cosmetics.Emotes; import org.saturnclient.ui.Element; import org.saturnclient.ui.ElementContext; import org.saturnclient.ui.RenderScope; import org.saturnclient.ui.SaturnScreen; import org.saturnclient.ui.anim.Fade; import org.saturnclient.ui.anim.SlideY; import org.saturnclient.ui.components.EmoteTile; import org.saturnclient.ui.elements.AnimationStagger; import org.saturnclient.ui.elements.ImageTexture; import org.saturnclient.ui.elements.Label; import org.saturnclient.ui.resources.Fonts; import org.saturnclient.ui.resources.Textures; /** * A 3x3 wheel of emotes to play, with the page controls in the middle. The * emote wheel key opens and closes it (in the version's key handler). */ public class EmoteWheel extends SaturnScreen { private static final int GAP = 10; private static final int GRID = EmoteTile.SIZE * 3 + GAP * 2; private static final int LOGO = 48; /** The grid's cells in the order emotes fill them, around the middle one */ private static final int[][] SLOTS = { { 0, 0 }, { 1, 0 }, { 2, 0 }, { 0, 1 }, { 2, 1 }, { 0, 2 }, { 1, 2 }, { 2, 2 } }; private int page = 0; public EmoteWheel() { super("Emote Wheel"); } @Override protected FloatProperty getScaleSetting() { return Config.hudMenuScale; } @Override public void ui() { List emotes = Emotes.availableEmotes; int left = (width - GRID) / 2; int top = (height - GRID) / 2 + LOGO / 2; draw(new ImageTexture(Config.getLogo()) .dimensions(LOGO, LOGO) .position((width - LOGO) / 2, top - 20 - LOGO) .animation(new SlideY(AnimationConfig.logo, -12))); if (emotes.isEmpty()) { String message = "No emotes available"; int x = (width - (int) (Fonts.getWidth(message, Theme.getBodyFont()) * 0.7f)) / 2; draw(new Label(() -> message, 0.7f, Theme::getBodyFont, Theme::getMutedFg).position(x, top + GRID / 2)); return; } int pages = (emotes.size() + SLOTS.length - 1) / SLOTS.length; page = Math.min(page, pages - 1); AnimationStagger grid = new AnimationStagger(AnimationConfig.emoteWheel); for (int slot = 0; slot < SLOTS.length; slot++) { int index = page * SLOTS.length + slot; if (index >= emotes.size()) { break; } String emote = emotes.get(index); grid.draw(new EmoteTile(emote, () -> play(emote)) .position(SLOTS[slot][0] * (EmoteTile.SIZE + GAP), SLOTS[slot][1] * (EmoteTile.SIZE + GAP)) .animation(new SlideY(AnimationConfig.emoteWheel, 12))); } draw(grid.position(left, top)); draw(new PageControl(page, pages, () -> turnPage(-1), () -> turnPage(1)) .position(left + EmoteTile.SIZE + GAP, top + EmoteTile.SIZE + GAP) .animation(new Fade(AnimationConfig.emoteWheel))); } private void play(String emote) { Providers.saturn.playEmote(ServiceClient.uuid, emote); ServiceClient.emote(emote); provider.close(); } private void turnPage(int direction) { page += direction; init(); } @Override public boolean shouldPause() { return false; } /** * The middle of the wheel: "Emotes", and the page with arrows to turn it * when there's more than one. */ private static class PageControl extends Element { private static final int ARROW = 16; private static final int ARROW_AREA = 26; private final int page; private final int pages; private final Runnable onPrevious; private final Runnable onNext; PageControl(int page, int pages, Runnable onPrevious, Runnable onNext) { this.page = page; this.pages = pages; this.onPrevious = onPrevious; this.onNext = onNext; this.width = EmoteTile.SIZE; this.height = EmoteTile.SIZE; } @Override public void render(RenderScope renderScope, ElementContext ctx) { renderScope.drawGlassRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, Config.widgetBlur.value, 1, Theme.getPanelBg(), Theme.getBorder()); float titleScale = 0.54f; drawCentered(renderScope, "EMOTES", titleScale, pages > 1 ? 24 : (height - 10) / 2, Theme.getMutedFg()); if (pages <= 1) { return; } drawCentered(renderScope, (page + 1) + " / " + pages, 0.66f, 48, Theme.FOREGROUND.value); int arrowY = 48 + ((int) (Fonts.getHeight() * 0.66f) - ARROW) / 2; drawArrow(renderScope, ctx, Textures.ICON_CHEVRON_LEFT, 4, arrowY, canGoBack()); drawArrow(renderScope, ctx, Textures.ICON_CHEVRON_RIGHT, width - 4 - ARROW, arrowY, canGoForward()); } private void drawArrow(RenderScope renderScope, ElementContext ctx, IdentifierRef icon, int x, int y, boolean enabled) { boolean hovered = enabled && ctx.isHovering(x - 5, 0, ARROW_AREA, height); int color = !enabled ? Theme.withAlpha(0.2f, Theme.FOREGROUND.value) : hovered ? Theme.ACCENT.value : Theme.getMutedFg(); renderScope.drawTexture(icon, x, y, 0, 0, ARROW, ARROW, color); } private void drawCentered(RenderScope renderScope, String text, float scale, int y, int color) { int x = (width - (int) (Fonts.getWidth(text, Theme.FONT.value) * scale)) / 2; renderScope.drawText(scale, text, x, y, Theme.FONT.value, color); } @Override public void click(int mouseX, int mouseY) { if (mouseX < ARROW_AREA && canGoBack()) { onPrevious.run(); } else if (mouseX > width - ARROW_AREA && canGoForward()) { onNext.run(); } } private boolean canGoBack() { return page > 0; } private boolean canGoForward() { return page < pages - 1; } } }