Fix and restyle the emote wheel
The slot index was taken before the middle slot was skipped, so page one showed emotes 0-4 and 6-8: emote 5 never appeared and emote 8 showed on two pages. Emotes now fill the eight cells around the middle in order, and slots past the last emote aren't drawn, so they can't play a null emote. Emotes show as EmoteTiles: the preview in its own colors (it was tinted, almost black on hover), the name under it, an accent outline on hover, and an icon when an emote has no preview. The middle cell holds the page and its arrows when there's more than one page. Emote names and cosmetic names share Utils.nameFromId. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -1,21 +1,43 @@
|
||||
package org.saturnclient.ui.screens;
|
||||
|
||||
import org.saturnclient.cosmetics.Emotes;
|
||||
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.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.Utils;
|
||||
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.TextureButton;
|
||||
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 {
|
||||
int page = 1;
|
||||
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");
|
||||
@@ -23,88 +45,131 @@ public class EmoteWheel extends SaturnScreen {
|
||||
|
||||
@Override
|
||||
public void ui() {
|
||||
int row = 0;
|
||||
int col = 0;
|
||||
|
||||
// =============================
|
||||
// Logo
|
||||
// =============================
|
||||
|
||||
draw(new ImageTexture(Textures.LOGO_TEXT)
|
||||
.dimensions(98, 10)
|
||||
.center(width, height, 0, -105)
|
||||
.animation(new Fade(AnimationConfig.logo.duration.value)));
|
||||
List<String> emotes = Emotes.availableEmotes;
|
||||
int left = (width - GRID) / 2;
|
||||
int top = (height - GRID) / 2 + LOGO / 2;
|
||||
|
||||
draw(new ImageTexture(Config.getLogo())
|
||||
.dimensions(98, 98)
|
||||
.center(width, height, 0, -149)
|
||||
.animation(new SlideY(AnimationConfig.logo, -20)));
|
||||
.dimensions(LOGO, LOGO)
|
||||
.position((width - LOGO) / 2, top - 20 - LOGO)
|
||||
.animation(new SlideY(AnimationConfig.logo, -12)));
|
||||
|
||||
// =============================
|
||||
// Navigation Buttons
|
||||
// =============================
|
||||
|
||||
draw(new TextureButton(Textures.LEFT, () -> {
|
||||
if (page > 1) {
|
||||
page--;
|
||||
elements.clear();
|
||||
ui();
|
||||
}
|
||||
}).dimensions(30, 30)
|
||||
.center(width, height, -50, 105)
|
||||
.animation(new Fade(500)));
|
||||
|
||||
draw(new TextureButton(Textures.RIGHT, () -> {
|
||||
if ((page * 8) < Emotes.availableEmotes.size()) {
|
||||
page++;
|
||||
elements.clear();
|
||||
ui();
|
||||
}
|
||||
}).dimensions(30, 30)
|
||||
.center(width, height, 50, 105)
|
||||
.animation(new Fade(500)));
|
||||
|
||||
// =============================
|
||||
// Emote Grid (Staggered)
|
||||
// =============================
|
||||
|
||||
AnimationStagger emoteStagger = new AnimationStagger(AnimationConfig.emoteWheel);
|
||||
|
||||
while (row < 3) {
|
||||
|
||||
int idx = (row * 3 + col) + ((page - 1) * 8);
|
||||
|
||||
if (row == 1 && col == 1) {
|
||||
col++;
|
||||
}
|
||||
|
||||
String emote = Utils.getOrNull(Emotes.availableEmotes, idx);
|
||||
|
||||
emoteStagger.draw(
|
||||
new TextureButton(Textures.getEmotePreview(emote), () -> {
|
||||
Providers.saturn.playEmote(ServiceClient.uuid, emote);
|
||||
ServiceClient.emote(emote);
|
||||
provider.close();
|
||||
})
|
||||
.dimensions(70, 70)
|
||||
.center(width, height,
|
||||
-80 + (col * 80),
|
||||
-35 + (row * 80))
|
||||
.animation(new SlideY(AnimationConfig.emoteWheel, 12)));
|
||||
|
||||
if (col < 2) {
|
||||
col++;
|
||||
} else {
|
||||
col = 0;
|
||||
row++;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
draw(emoteStagger);
|
||||
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.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user