Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b14c5c4ffa | ||
|
|
fd9ec3222c | ||
|
|
917eba1e17 | ||
|
|
2ef28c9e14 | ||
|
|
e395068e76 | ||
|
|
e8f686b2d3 | ||
|
|
df5afe99e3 | ||
|
|
653738568a | ||
|
|
a6a324a61a | ||
|
|
b0d75af8c4 | ||
|
|
c973e053aa | ||
|
|
bbf6b88ed3 | ||
|
|
ba71a5b838 | ||
|
|
6d5df3057e | ||
|
|
2fceb58024 | ||
|
|
7e98bbcd6c | ||
|
|
9ede612089 | ||
|
|
ed2914aaf7 | ||
|
|
69c830d466 | ||
|
|
7d0bfd26c9 | ||
|
|
7cc37b7c74 | ||
|
|
524fc36bf3 | ||
|
|
7bc308155e | ||
|
|
4d9c629eaf | ||
|
|
5c0912f0cf | ||
|
|
3436054112 | ||
|
|
adedd688a7 | ||
|
|
a7ad458627 | ||
|
|
320b2fd9a5 | ||
|
|
a7e3e537b1 | ||
|
|
ef0ead6bf8 | ||
|
|
136621cc8d | ||
|
|
25d288da92 | ||
|
|
f0971ff128 | ||
|
|
2f4ea77e6f | ||
|
|
05dbec028a | ||
|
|
caf472da96 | ||
|
|
c3667f8f66 | ||
|
|
85c7057ba1 | ||
|
|
55b78989ce | ||
|
|
8b58fe919d | ||
|
|
6bfdfc40c9 | ||
|
|
351507d60d | ||
|
|
33c09b6922 | ||
|
|
06a90d8e51 | ||
|
|
7264c7b38b | ||
|
|
a413648be9 |
@@ -22,6 +22,12 @@ public interface SaturnProvider {
|
||||
/** The Saturn Client mod version, e.g. {@code 0.1.0-beta3+1.21.11}. */
|
||||
public String getModVersion();
|
||||
|
||||
/**
|
||||
* The signed-in player's 64x64 skin texture, or the default skin until
|
||||
* theirs has loaded.
|
||||
*/
|
||||
public IdentifierRef getPlayerSkin();
|
||||
|
||||
/** Shows a warning toast. Safe to call from any thread. */
|
||||
public void showWarning(String title, String message);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package org.saturnclient.common.ref.game;
|
||||
|
||||
/**
|
||||
* An item stack. Its methods are implemented in ItemStackMixin, so their names must not match any of
|
||||
* ItemStack's own methods.
|
||||
*/
|
||||
public interface ItemStackRef {
|
||||
public boolean isEmpty();
|
||||
public int getMaxDamage();
|
||||
public int getDamage();
|
||||
public boolean isEmptyStack();
|
||||
public int getMaxItemDamage();
|
||||
public int getItemDamage();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
package org.saturnclient.common.ref.render;
|
||||
|
||||
/**
|
||||
* The game window. Its methods are implemented in WindowMixin, so their names must not match any of
|
||||
* Window's own methods.
|
||||
*/
|
||||
public interface WindowRef {
|
||||
public int getWidth();
|
||||
/** The window's width in screen coordinates */
|
||||
public int getWindowWidth();
|
||||
|
||||
public int getHeight();
|
||||
/** The window's height in screen coordinates */
|
||||
public int getWindowHeight();
|
||||
|
||||
public int getFramebufferWidth();
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package org.saturnclient.config;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.saturnclient.config.property.CurveProperty;
|
||||
import org.saturnclient.config.property.IntProperty;
|
||||
import org.saturnclient.config.property.Property;
|
||||
import org.saturnclient.config.property.SelectProperty;
|
||||
|
||||
public class AnimationConfig {
|
||||
public static ConfigManager config;
|
||||
@@ -25,6 +29,10 @@ public class AnimationConfig {
|
||||
// Emote wheel
|
||||
public static final AnimationConfig emoteWheel = new AnimationConfig(true, 700, 40);
|
||||
|
||||
/** Every menu's animation, in the order they're shown in the settings */
|
||||
public static final List<AnimationConfig> ALL = List.of(logo, mainMenu, modMenu, shiftMenu, cosmeticsMenu,
|
||||
emoteWheel);
|
||||
|
||||
public static void init(ConfigManager parent) {
|
||||
config = new ConfigManager(parent, "Animations");
|
||||
|
||||
@@ -39,18 +47,36 @@ public class AnimationConfig {
|
||||
// Animation properties
|
||||
public final IntProperty duration;
|
||||
public final IntProperty stagger;
|
||||
public final SelectProperty curve;
|
||||
public final CurveProperty curve;
|
||||
|
||||
public AnimationConfig(boolean enabled, int delay, int stagger) {
|
||||
this.duration = Property.integer(delay);
|
||||
this.stagger = Property.integer(stagger);
|
||||
this.curve = Property.select(1, "Ease In Out Cubic", "Ease Out Cubic", "Ease In Out Back");
|
||||
this.duration = Property.integer(delay).range(0, 2000, 50).unit("ms");
|
||||
this.stagger = Property.integer(stagger).range(0, 300, 10).unit("ms");
|
||||
this.curve = Property.curve(1);
|
||||
}
|
||||
|
||||
/** The menu this animates, like "Mod Menu" */
|
||||
public String name;
|
||||
|
||||
/**
|
||||
* This animation's settings under short names ("Duration"), for showing
|
||||
* them grouped under {@link #name}. They're saved under longer names
|
||||
* ("Mod Menu Duration"), which stay as they are so saved settings keep
|
||||
* loading.
|
||||
*/
|
||||
public Map<String, Property> getProperties() {
|
||||
Map<String, Property> properties = new LinkedHashMap<>();
|
||||
properties.put("Duration", duration);
|
||||
properties.put("Stagger", stagger);
|
||||
properties.put("Curve", curve);
|
||||
return properties;
|
||||
}
|
||||
|
||||
public void init(String name) {
|
||||
this.name = name;
|
||||
// config.property("Enable " + name + " Animations", this.enabled);
|
||||
config.property(name + " Duration", this.duration);
|
||||
config.property(name + " Stagger", this.stagger);
|
||||
config.property(name + " Curve", this.curve);
|
||||
config.property(name + " Duration", this.duration, "How long the animation takes");
|
||||
config.property(name + " Stagger", this.stagger, "Delay between items appearing one after another");
|
||||
config.property(name + " Curve", this.curve, "Easing of the animation");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import org.saturnclient.common.provider.GLFWProvider;
|
||||
import org.saturnclient.common.provider.Providers;
|
||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||
import org.saturnclient.config.property.BoolProperty;
|
||||
import org.saturnclient.config.property.FloatProperty;
|
||||
import org.saturnclient.config.property.IntProperty;
|
||||
import org.saturnclient.config.property.KeybindingProperty;
|
||||
import org.saturnclient.config.property.Property;
|
||||
import org.saturnclient.ui.resources.Textures;
|
||||
@@ -17,8 +19,33 @@ public class Config {
|
||||
public static BoolProperty saturnTitleScreen = Property.bool(true);
|
||||
public static BoolProperty cloakPhysics = Property.bool(true);
|
||||
public static BoolProperty stagger = Property.bool(true);
|
||||
/** The world or panorama behind Saturn's screens, faded in as they open */
|
||||
public static IntProperty backgroundBlur = Property.integer(5).range(0, 10);
|
||||
/** Glass behind translucent menus (panels), and widgets (buttons, the dock) */
|
||||
public static IntProperty menuBlur = Property.integer(8).range(0, 20);
|
||||
public static IntProperty widgetBlur = Property.integer(6).range(0, 20);
|
||||
// How big Saturn's screens are, relative to Minecraft's GUI scale
|
||||
public static FloatProperty titleScale = guiScale();
|
||||
public static FloatProperty menuScale = guiScale();
|
||||
public static FloatProperty hudMenuScale = guiScale();
|
||||
|
||||
/** Added in 0.1.3, so a config without it was saved by an older version */
|
||||
static final String MENU_BLUR = "Menu blur";
|
||||
public static KeybindingProperty openEmoteWheel = Property.keybinding(GLFWProvider.GLFW_KEY_B);
|
||||
|
||||
private static FloatProperty guiScale() {
|
||||
return Property.floatProp(1f).range(0.5f, 2f, 0.05f).unit("×");
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether any glass can be drawn: a glass blur is on, and a background it
|
||||
* goes behind isn't solid.
|
||||
*/
|
||||
public static boolean usesGlass() {
|
||||
boolean translucent = Theme.getPanelBg() >>> 24 < 0xFF || Theme.PRIMARY.value >>> 24 < 0xFF;
|
||||
return translucent && (menuBlur.value > 0 || widgetBlur.value > 0);
|
||||
}
|
||||
|
||||
public static IdentifierRef getLogo() {
|
||||
return realisticLogo.value ? Textures.REALISTIC_LOGO : Textures.LOGO;
|
||||
}
|
||||
@@ -27,11 +54,17 @@ public class Config {
|
||||
File configFile = new File(Providers.saturn.getClient().getRunDirectory(), "saturn.json");
|
||||
config = new ConfigManager(configFile, "Saturn Client");
|
||||
|
||||
config.property("Realistic logo", realisticLogo);
|
||||
config.property("Saturn client title screen", saturnTitleScreen);
|
||||
config.property("Open Emote Wheel", openEmoteWheel);
|
||||
config.property("Cloak Physics", cloakPhysics);
|
||||
config.property("Stagger Animations", stagger);
|
||||
config.property("Realistic logo", realisticLogo, "Use the realistic Saturn logo");
|
||||
config.property("Saturn client title screen", saturnTitleScreen, "Replace the title screen with Saturn's");
|
||||
config.property("Open Emote Wheel", openEmoteWheel, "The key that opens the emote wheel");
|
||||
config.property("Cloak Physics", cloakPhysics, "Let cloaks swing as you move");
|
||||
config.property("Stagger Animations", stagger, "Animate list items one after another");
|
||||
config.property("World blur", backgroundBlur, "How much the world or panorama behind Saturn's screens is blurred");
|
||||
config.property(MENU_BLUR, menuBlur, "How much the game is blurred through translucent menus");
|
||||
config.property("Widget blur", widgetBlur, "How much the game is blurred through translucent buttons and the dock");
|
||||
config.property("Title screen scale", titleScale, "How big the title screen is, relative to Minecraft's GUI scale");
|
||||
config.property("Menu scale", menuScale, "How big the mods, settings and cosmetics menus are, relative to Minecraft's GUI scale");
|
||||
config.property("HUD menu scale", hudMenuScale, "How big the emote wheel and shift menu are, relative to Minecraft's GUI scale");
|
||||
|
||||
// Initialize a sub namespace for theme
|
||||
Theme.init(config);
|
||||
|
||||
@@ -70,6 +70,14 @@ public class ConfigManager {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a property with the line shown under its name
|
||||
*/
|
||||
public Property property(String name, Property value, String description) {
|
||||
value.description = description;
|
||||
return property(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add empty namespace
|
||||
*/
|
||||
@@ -148,10 +156,17 @@ public class ConfigManager {
|
||||
* Load only a single property immediately after creation
|
||||
*/
|
||||
private void loadSingleProperty(String name, Property prop) {
|
||||
JsonNode element = getSaved(name);
|
||||
|
||||
if (element != null) {
|
||||
prop.loadFromJson(element);
|
||||
}
|
||||
}
|
||||
|
||||
private JsonNode getSaved(String name) {
|
||||
JsonNode source = cachedJson != null ? cachedJson : loadAndCache();
|
||||
if (source == null || !source.isObject())
|
||||
return;
|
||||
return null;
|
||||
|
||||
JsonNode currentScope = source;
|
||||
|
||||
@@ -162,16 +177,12 @@ public class ConfigManager {
|
||||
if (next != null && next.isObject()) {
|
||||
currentScope = next;
|
||||
} else {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
JsonNode element = currentScope.get(name);
|
||||
|
||||
if (element != null) {
|
||||
prop.loadFromJson(element);
|
||||
}
|
||||
return currentScope.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,53 +1,88 @@
|
||||
package org.saturnclient.config;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.saturnclient.config.property.ColorProperty;
|
||||
import org.saturnclient.config.property.IntProperty;
|
||||
import org.saturnclient.config.property.Property;
|
||||
import org.saturnclient.config.property.SelectProperty;
|
||||
|
||||
public class Theme {
|
||||
// Main UI background (slightly warm dark to match Saturn tone)
|
||||
public static final ColorProperty BACKGROUND = Property.color(0xFF12100D);
|
||||
public static ConfigManager config;
|
||||
|
||||
// Main UI background (a near-neutral dark, so the warm world showing through
|
||||
// doesn't muddy it), translucent over the world
|
||||
public static final ColorProperty BACKGROUND = Property.color(0xD1131417);
|
||||
|
||||
// Default text color
|
||||
public static final ColorProperty FOREGROUND = Property.color(0xFFF2E6D2);
|
||||
public static final ColorProperty FOREGROUND = Property.color(0xFFECEFF3);
|
||||
|
||||
// Widget default background (normal state)
|
||||
public static final ColorProperty PRIMARY = Property.color(0xFF1C1915);
|
||||
// Widget default background (normal state), translucent over the world
|
||||
public static final ColorProperty PRIMARY = Property.color(0xB81E2025);
|
||||
|
||||
// Widget text color
|
||||
public static final ColorProperty PRIMARY_FG = Property.color(0xFFEAD9B8);
|
||||
public static final ColorProperty PRIMARY_FG = Property.color(0xFFD5DAE1);
|
||||
|
||||
// Saturn brand color (hovered / enabled / selected)
|
||||
public static final ColorProperty ACCENT = Property.color(0xFFE2BF7D);
|
||||
// Saturn brand color, a pale frost (hovered / enabled / selected)
|
||||
public static final ColorProperty ACCENT = Property.color(0xFFD3DCE6);
|
||||
|
||||
// Text/icons shown on accent background
|
||||
public static final ColorProperty ACCENT_FG = Property.color(0xFF1A140A);
|
||||
public static final ColorProperty ACCENT_FG = Property.color(0xFF15181C);
|
||||
|
||||
// Misc colors
|
||||
public static final ColorProperty SCROLL = Property.color(0xFFE2BF7D);
|
||||
// Misc colors: the one warm touch, Saturn's butterscotch
|
||||
public static final ColorProperty SCROLL = Property.color(0xFFD9B98A);
|
||||
|
||||
// UI metrics
|
||||
public static final IntProperty BG_RADIUS = Property.integer(12);
|
||||
public static final IntProperty WIDGET_RADIUS = Property.integer(10);
|
||||
public static final IntProperty BG_RADIUS = Property.integer(12).range(0, 24);
|
||||
public static final IntProperty WIDGET_RADIUS = Property.integer(10).range(0, 20);
|
||||
|
||||
// Font
|
||||
public static final SelectProperty FONT = Property.font(2);
|
||||
|
||||
/**
|
||||
* A full set of theme colors that can be applied in one go.
|
||||
*/
|
||||
public record Preset(String name, int background, int foreground, int primary, int primaryFg, int accent,
|
||||
int accentFg, int scroll) {
|
||||
|
||||
public void apply() {
|
||||
BACKGROUND.value = background;
|
||||
FOREGROUND.value = foreground;
|
||||
PRIMARY.value = primary;
|
||||
PRIMARY_FG.value = primaryFg;
|
||||
ACCENT.value = accent;
|
||||
ACCENT_FG.value = accentFg;
|
||||
SCROLL.value = scroll;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return BACKGROUND.value == background && FOREGROUND.value == foreground && PRIMARY.value == primary
|
||||
&& PRIMARY_FG.value == primaryFg && ACCENT.value == accent && ACCENT_FG.value == accentFg
|
||||
&& SCROLL.value == scroll;
|
||||
}
|
||||
}
|
||||
|
||||
public static final List<Preset> PRESETS = List.of(
|
||||
new Preset("Saturn", 0xD1131417, 0xFFECEFF3, 0xB81E2025, 0xFFD5DAE1, 0xFFD3DCE6, 0xFF15181C, 0xFFD9B98A),
|
||||
new Preset("Saturn Gold", 0xD112100D, 0xFFF2E6D2, 0xB81C1915, 0xFFEAD9B8, 0xFFE2BF7D, 0xFF1A140A, 0xFFE2BF7D),
|
||||
new Preset("Mono", 0xD1121212, 0xFFF0F0F0, 0xB81C1C1C, 0xFFDADADA, 0xFFF0F0F0, 0xFF121212, 0xFFF0F0F0),
|
||||
new Preset("Midnight", 0xD1121418, 0xFFE5E9F0, 0xB81C2027, 0xFFD8DEE9, 0xFF8FAFCF, 0xFF10151D, 0xFF8FAFCF),
|
||||
new Preset("Amethyst", 0xD1141317, 0xFFECE8F1, 0xB81F1D24, 0xFFDAD3E3, 0xFFB49CCB, 0xFF19131F, 0xFFB49CCB));
|
||||
|
||||
public static void init(ConfigManager parent) {
|
||||
ConfigManager config = new ConfigManager(parent, "UI Style");
|
||||
config.property("Background", BACKGROUND);
|
||||
config.property("Foreground", FOREGROUND);
|
||||
config.property("Primary", PRIMARY);
|
||||
config.property("Primary Foreground", PRIMARY_FG);
|
||||
config.property("Accent", ACCENT);
|
||||
config.property("Accent Foreground", ACCENT_FG);
|
||||
config.property("Scrollbar Color", SCROLL);
|
||||
config = new ConfigManager(parent, "UI Style");
|
||||
config.property("Background", BACKGROUND, "Panels and screens");
|
||||
config.property("Foreground", FOREGROUND, "Text");
|
||||
config.property("Primary", PRIMARY, "Buttons and cards");
|
||||
config.property("Primary Foreground", PRIMARY_FG, "Text on buttons and cards");
|
||||
config.property("Accent", ACCENT, "Selected and enabled items");
|
||||
config.property("Accent Foreground", ACCENT_FG, "Text on the accent color");
|
||||
config.property("Scrollbar Color", SCROLL, "The scrollbar's handle");
|
||||
|
||||
config.property("Background Radius", BG_RADIUS);
|
||||
config.property("Widget Radius", WIDGET_RADIUS);
|
||||
config.property("Background Radius", BG_RADIUS, "Corner rounding of panels");
|
||||
config.property("Widget Radius", WIDGET_RADIUS, "Corner rounding of buttons and cards");
|
||||
|
||||
config.property("Font", FONT);
|
||||
config.property("Font", FONT, "Font of every Saturn screen");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,10 +100,19 @@ public class Theme {
|
||||
}
|
||||
|
||||
/**
|
||||
* Translucent background for panels drawn over the world
|
||||
* Background for panels drawn over the world, as translucent as the
|
||||
* background color is
|
||||
*/
|
||||
public static int getPanelBg() {
|
||||
return withAlpha(0.82f, BACKGROUND.value);
|
||||
return BACKGROUND.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Background for popups, which sit over other panels: the background color,
|
||||
* kept nearly solid so what's under it doesn't show through
|
||||
*/
|
||||
public static int getPopupBg() {
|
||||
return withAlpha(0.97f, BACKGROUND.value | 0xFF000000);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.saturnclient.config.property;
|
||||
|
||||
/**
|
||||
* An animation's easing curve. The options are in the order
|
||||
* {@code Animation.getCurve} takes them, and it's saved like any select.
|
||||
*/
|
||||
public class CurveProperty extends SelectProperty {
|
||||
|
||||
public CurveProperty(int defaultValue) {
|
||||
super(defaultValue, "Ease In Out Cubic", "Ease Out Cubic", "Ease In Out Back");
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,36 @@ public class FloatProperty extends Property {
|
||||
public float value;
|
||||
public final float defaultValue;
|
||||
|
||||
/** Bounds for a slider, when the setting has a sensible range */
|
||||
public Float min, max;
|
||||
public float step = 0.1f;
|
||||
/** Shown after the value, like "×" */
|
||||
public String unit = "";
|
||||
|
||||
public FloatProperty(float defaultValue) {
|
||||
this.value = defaultValue;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives the setting a range, so it's edited with a slider.
|
||||
*/
|
||||
public FloatProperty range(float min, float max, float step) {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.step = step;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FloatProperty unit(String unit) {
|
||||
this.unit = unit;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean hasRange() {
|
||||
return min != null && max != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
this.value = defaultValue;
|
||||
|
||||
@@ -7,11 +7,40 @@ public class IntProperty extends Property {
|
||||
public int value;
|
||||
public final int defaultValue;
|
||||
|
||||
/** Bounds for a slider, when the setting has a sensible range */
|
||||
public Integer min, max;
|
||||
public int step = 1;
|
||||
/** Shown after the value, like "ms" */
|
||||
public String unit = "";
|
||||
|
||||
public IntProperty(int defaultValue) {
|
||||
this.value = defaultValue;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives the setting a range, so it's edited with a slider.
|
||||
*/
|
||||
public IntProperty range(int min, int max) {
|
||||
return range(min, max, 1);
|
||||
}
|
||||
|
||||
public IntProperty range(int min, int max, int step) {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.step = step;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IntProperty unit(String unit) {
|
||||
this.unit = unit;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean hasRange() {
|
||||
return min != null && max != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
this.value = defaultValue;
|
||||
|
||||
@@ -8,4 +8,12 @@ public class NamedProperty {
|
||||
this.name = name;
|
||||
this.prop = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the line shown under the setting's name.
|
||||
*/
|
||||
public NamedProperty description(String description) {
|
||||
this.prop.description = description;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,9 @@ import java.util.Map;
|
||||
public abstract class Property {
|
||||
public boolean isReset = false;
|
||||
|
||||
/** One line under the setting's name saying what it does, or "" */
|
||||
public String description = "";
|
||||
|
||||
// ---------- Factory Methods ----------
|
||||
|
||||
public static NamespaceProperty namespace(Map<String, Property> value) {
|
||||
@@ -41,6 +44,10 @@ public abstract class Property {
|
||||
return select(value, "Default", "Inter", "Inter bold");
|
||||
}
|
||||
|
||||
public static CurveProperty curve(int value) {
|
||||
return new CurveProperty(value);
|
||||
}
|
||||
|
||||
public static SelectProperty select(Integer value, String... availableValues) {
|
||||
return new SelectProperty(value, availableValues);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,14 @@ public class Cloaks {
|
||||
availableCloaks.add(0, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* The cloak's texture, or null for no cloak ("", the "None" choice) and for a cloak this version
|
||||
* has no texture for, which would otherwise draw as the missing texture.
|
||||
*/
|
||||
public static IdentifierRef getCurrentCloakTexture(String cloakName) {
|
||||
if (cloakName == null || !Arrays.asList(ALL_CLOAKS).contains(cloakName)) {
|
||||
return null;
|
||||
}
|
||||
if (Arrays.asList(ANIMATED_CLOAKS).contains(cloakName)) {
|
||||
return IdentifierRef.ofSaturn("textures/cloaks/" + cloakName + ".gif");
|
||||
} else {
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.Optional;
|
||||
import org.saturnclient.common.provider.Providers;
|
||||
import org.saturnclient.emotes.core.Emote;
|
||||
import org.saturnclient.emotes.core.EmoteRegistry;
|
||||
import org.saturnclient.ui.Utils;
|
||||
|
||||
/**
|
||||
* Saturn's emotes, loaded from {@code assets/saturnclient/player_animations/<id>.json} (Emotecraft
|
||||
@@ -16,13 +17,20 @@ import org.saturnclient.emotes.core.EmoteRegistry;
|
||||
*/
|
||||
public class Emotes {
|
||||
private static final List<String> IDS = List.of("tpose", "back_flip", "facepalm", "cry", "giga_chad",
|
||||
"front_flip");
|
||||
"twerk", "get_the_griddy", "take_the_l", "dab", "wave", "salute", "shrug", "67");
|
||||
|
||||
private static final EmoteRegistry REGISTRY = new EmoteRegistry();
|
||||
|
||||
/** The emotes that loaded, in wheel order. */
|
||||
public static List<String> availableEmotes = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* The emote's name as shown to players.
|
||||
*/
|
||||
public static String getName(String id) {
|
||||
return id.equals("tpose") ? "T-Pose" : Utils.nameFromId(id);
|
||||
}
|
||||
|
||||
public static void initialize() {
|
||||
for (String id : IDS) {
|
||||
String path = "/assets/saturnclient/player_animations/" + id + ".json";
|
||||
|
||||
@@ -13,10 +13,10 @@ import org.saturnclient.config.property.NamedProperty;
|
||||
public class ModLayout {
|
||||
public IntProperty x = Property.integer(0);
|
||||
public IntProperty y = Property.integer(0);
|
||||
public FloatProperty scale = Property.floatProp(1.0f);
|
||||
public FloatProperty scale = Property.floatProp(1.0f).range(0.5f, 4f, 0.1f).unit("×");
|
||||
public ColorProperty bgColor = Property.color(0x00000000);
|
||||
public ColorProperty fgColor = Property.color(0xFFffffff);
|
||||
public IntProperty radius = Property.integer(0);
|
||||
public IntProperty radius = Property.integer(0).range(0, 20);
|
||||
public SelectProperty font = Property.font(0);
|
||||
public boolean renderBackground = true;
|
||||
|
||||
@@ -26,17 +26,22 @@ public class ModLayout {
|
||||
public NamedProperty prop() {
|
||||
// A LinkedHashMap, unlike Map.of, keeps the order the settings are shown in.
|
||||
Map<String, Property> props = new LinkedHashMap<>();
|
||||
props.put("X", x);
|
||||
props.put("Y", y);
|
||||
props.put("Scale", scale);
|
||||
props.put("Background Color", bgColor);
|
||||
props.put("Foreground Color", fgColor);
|
||||
props.put("Corner Radius", radius);
|
||||
props.put("Font", font);
|
||||
put(props, "X", x, "Distance from the left of the screen");
|
||||
put(props, "Y", y, "Distance from the top of the screen");
|
||||
put(props, "Scale", scale, "Size of the display");
|
||||
put(props, "Background Color", bgColor, "Fill behind the display");
|
||||
put(props, "Foreground Color", fgColor, "Color of the text");
|
||||
put(props, "Corner Radius", radius, "Rounding of the background's corners");
|
||||
put(props, "Font", font, "Font of the text");
|
||||
|
||||
return Property.namespace(props).named("In-Game Display");
|
||||
}
|
||||
|
||||
private static void put(Map<String, Property> props, String name, Property prop, String description) {
|
||||
prop.description = description;
|
||||
props.put(name, prop);
|
||||
}
|
||||
|
||||
public ModLayout() {
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ public class ArmorDisplayMod extends Mod implements HudMod {
|
||||
.tags("Utility")
|
||||
.requires(Providers.feature::player),
|
||||
enabled.named("Enabled"),
|
||||
useMainHand.named("Use Main Hand"),
|
||||
useMainHand.named("Use Main Hand").description("Also show the item in your main hand"),
|
||||
layout.prop());
|
||||
}
|
||||
|
||||
@@ -75,16 +75,16 @@ public class ArmorDisplayMod extends Mod implements HudMod {
|
||||
|
||||
int row = 0;
|
||||
|
||||
if (useMainHand.value && !mainHand.isEmpty()) {
|
||||
if (useMainHand.value && !mainHand.isEmptyStack()) {
|
||||
row = renderSlot(scope, mainHand, row);
|
||||
}
|
||||
if (!helmet.isEmpty())
|
||||
if (!helmet.isEmptyStack())
|
||||
row = renderSlot(scope, helmet, row);
|
||||
if (!chestplate.isEmpty())
|
||||
if (!chestplate.isEmptyStack())
|
||||
row = renderSlot(scope, chestplate, row);
|
||||
if (!leggings.isEmpty())
|
||||
if (!leggings.isEmptyStack())
|
||||
row = renderSlot(scope, leggings, row);
|
||||
if (!boots.isEmpty())
|
||||
if (!boots.isEmptyStack())
|
||||
row = renderSlot(scope, boots, row);
|
||||
}
|
||||
|
||||
@@ -94,8 +94,8 @@ public class ArmorDisplayMod extends Mod implements HudMod {
|
||||
private int renderSlot(RenderScope scope, ItemStackRef item, int row) {
|
||||
scope.drawItem(item, 0, 15 * row);
|
||||
|
||||
if (item.getMaxDamage() > 0) {
|
||||
int remaining = item.getMaxDamage() - item.getDamage();
|
||||
if (item.getMaxItemDamage() > 0) {
|
||||
int remaining = item.getMaxItemDamage() - item.getItemDamage();
|
||||
scope.drawText(0.5f, String.valueOf(remaining),
|
||||
17, (15 * row) + 3,
|
||||
layout.font.value, layout.fgColor.value);
|
||||
|
||||
@@ -31,8 +31,8 @@ public class ClockMod extends Mod implements HudMod {
|
||||
.version("v0.2.0")
|
||||
.tags("Utility"),
|
||||
enabled.named("Enabled"),
|
||||
format.named("Format"),
|
||||
showSeconds.named("Show seconds"),
|
||||
format.named("Format").description("24-hour or 12-hour time"),
|
||||
showSeconds.named("Show seconds").description("Include seconds in the time"),
|
||||
layout.prop());
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ public class CoordinatesMod extends Mod implements HudMod {
|
||||
.tags("Utility")
|
||||
.requires(Providers.feature::player),
|
||||
enabled.named("Enabled"),
|
||||
displayMethod.named("Display Method"),
|
||||
displayMethod.named("Display Method").description("One line or one per axis, with or without labels"),
|
||||
layout.prop());
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ import java.util.function.Function;
|
||||
import org.saturnclient.common.provider.Providers;
|
||||
import org.saturnclient.common.provider.GLFWProvider;
|
||||
import org.saturnclient.config.property.BoolProperty;
|
||||
import org.saturnclient.config.property.CurveProperty;
|
||||
import org.saturnclient.config.property.FloatProperty;
|
||||
import org.saturnclient.config.property.IntProperty;
|
||||
import org.saturnclient.config.property.KeybindingProperty;
|
||||
import org.saturnclient.config.property.Property;
|
||||
import org.saturnclient.config.property.SelectProperty;
|
||||
import org.saturnclient.mod.Mod;
|
||||
import org.saturnclient.mod.ModSpec;
|
||||
import org.saturnclient.common.feature.RenderFeature;
|
||||
@@ -22,10 +22,10 @@ public class FreelookMod extends Mod {
|
||||
private static final BoolProperty toggle = Property.bool(true);
|
||||
private static final KeybindingProperty freelookKey = Property.keybinding(GLFWProvider.GLFW_KEY_H);
|
||||
|
||||
public static final FloatProperty zoomLevel = Property.floatProp(2.0f);
|
||||
public static final IntProperty zoomInDuration = Property.integer(500);
|
||||
public static final IntProperty zoomOutDuration = Property.integer(300);
|
||||
public static final SelectProperty curve = Property.select(1, "Ease In Out Cubic", "Ease Out Cubic", "Ease In Out Back");
|
||||
public static final FloatProperty zoomLevel = Property.floatProp(2.0f).range(1f, 5f, 0.1f).unit("×");
|
||||
public static final IntProperty zoomInDuration = Property.integer(500).range(0, 2000, 50).unit("ms");
|
||||
public static final IntProperty zoomOutDuration = Property.integer(300).range(0, 2000, 50).unit("ms");
|
||||
public static final CurveProperty curve = Property.curve(1);
|
||||
|
||||
// --- State Variables ---
|
||||
private static boolean isFreeLooking = false;
|
||||
@@ -43,12 +43,12 @@ public class FreelookMod extends Mod {
|
||||
.version("v0.3.1")
|
||||
.requires(Providers.feature::render),
|
||||
enabled.named("Enabled"),
|
||||
toggle.named("Toggle freelook"),
|
||||
freelookKey.named("Freelook Keybinding"),
|
||||
zoomLevel.named("Zoom Level"),
|
||||
zoomInDuration.named("Zoom In Duration"),
|
||||
zoomOutDuration.named("Zoom Out Duration"),
|
||||
curve.named("Curve"));
|
||||
toggle.named("Toggle freelook").description("Press the key once instead of holding it"),
|
||||
freelookKey.named("Freelook Keybinding").description("The key that starts freelook"),
|
||||
zoomLevel.named("Zoom Level").description("Zoom when freelook starts in first person"),
|
||||
zoomInDuration.named("Zoom In Duration").description("How long that zoom takes to ease in"),
|
||||
zoomOutDuration.named("Zoom Out Duration").description("How long that zoom takes to ease out"),
|
||||
curve.named("Curve").description("Easing of the zoom"));
|
||||
}
|
||||
|
||||
// --- Lifecycle Methods ---
|
||||
|
||||
@@ -19,7 +19,7 @@ import org.saturnclient.mod.ModSpec;
|
||||
public class FullbrightMod extends Mod {
|
||||
|
||||
private static final BoolProperty enabled = Property.bool(false);
|
||||
private static final IntProperty brightness = Property.integer(100);
|
||||
private static final IntProperty brightness = Property.integer(100).range(0, 1000, 10);
|
||||
|
||||
public FullbrightMod() {
|
||||
super(
|
||||
@@ -28,7 +28,7 @@ public class FullbrightMod extends Mod {
|
||||
.tags("Camera")
|
||||
.version("v0.2.0"),
|
||||
enabled.named("Enabled"),
|
||||
brightness.named("Brightness %"));
|
||||
brightness.named("Brightness %").description("How bright the world gets"));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
@@ -31,8 +31,8 @@ public class HealthDisplayMod extends Mod implements HudMod {
|
||||
.tags("Utility")
|
||||
.requires(Providers.feature::player),
|
||||
enabled.named("Enabled"),
|
||||
displayMode.named("Display mode"),
|
||||
decimals.named("Decimals"),
|
||||
displayMode.named("Display mode").description("Show health points or hearts"),
|
||||
decimals.named("Decimals").description("Decimal places shown"),
|
||||
layout.prop());
|
||||
}
|
||||
|
||||
|
||||
@@ -54,11 +54,11 @@ public class KeystrokesMod extends Mod implements HudMod {
|
||||
.tags("Utility")
|
||||
.requires(Providers.feature::player),
|
||||
enabled.named("Enabled"),
|
||||
showMouse.named("Show mouse clicks"),
|
||||
showSpace.named("Show space"),
|
||||
showMouse.named("Show mouse clicks").description("Show the left and right mouse buttons"),
|
||||
showSpace.named("Show space").description("Show the space bar"),
|
||||
layout.prop(),
|
||||
clickFg.named("Clicked fg"),
|
||||
clickBg.named("Clicked bg"));
|
||||
clickFg.named("Clicked fg").description("Text color of a pressed key"),
|
||||
clickBg.named("Clicked bg").description("Background of a pressed key"));
|
||||
layout.renderBackground = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,16 +58,16 @@ public class NametagsMod extends Mod {
|
||||
.tags("Camera", "Utility")
|
||||
.version("v0.2.0"),
|
||||
enabled.named("Enabled"),
|
||||
healthDisplay.named("Display health"),
|
||||
players.named("Players"),
|
||||
hostile.named("Hostile"),
|
||||
passive.named("Passive"),
|
||||
heartEmoji.named("Heart emoji"),
|
||||
obfuscate.named("Obfuscate enemy names"),
|
||||
unit.named("Unit"),
|
||||
format.named("Format"),
|
||||
nameColor.named("Name color"),
|
||||
healthColor.named("Health color"));
|
||||
healthDisplay.named("Display health").description("Show health after the name"),
|
||||
players.named("Players").description("Show health on players"),
|
||||
hostile.named("Hostile").description("Show health on hostile mobs"),
|
||||
passive.named("Passive").description("Show health on passive mobs"),
|
||||
heartEmoji.named("Heart emoji").description("Add a heart after the health"),
|
||||
obfuscate.named("Obfuscate enemy names").description("Scramble names into unreadable text"),
|
||||
unit.named("Unit").description("Show health points or hearts"),
|
||||
format.named("Format").description("Value, value out of total, or percent"),
|
||||
nameColor.named("Name color").description("Color of the name"),
|
||||
healthColor.named("Health color").description("Color of the health"));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
@@ -34,9 +34,9 @@ public class SpeedometerMod extends Mod implements HudMod {
|
||||
.tags("Utility")
|
||||
.requires(Providers.feature::player),
|
||||
enabled.named("Enabled"),
|
||||
axis.named("Speed type"),
|
||||
unitText.named("Unit text"),
|
||||
showLabel.named("Show speed label"),
|
||||
axis.named("Speed type").description("All movement, only horizontal or only vertical"),
|
||||
unitText.named("Unit text").description("Unit shown after the speed"),
|
||||
showLabel.named("Show speed label").description("Show “Speed:” before the value"),
|
||||
layout.prop());
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ import java.util.function.Function;
|
||||
|
||||
import org.saturnclient.common.provider.GLFWProvider;
|
||||
import org.saturnclient.config.property.BoolProperty;
|
||||
import org.saturnclient.config.property.CurveProperty;
|
||||
import org.saturnclient.config.property.FloatProperty;
|
||||
import org.saturnclient.config.property.IntProperty;
|
||||
import org.saturnclient.config.property.KeybindingProperty;
|
||||
import org.saturnclient.config.property.Property;
|
||||
import org.saturnclient.config.property.SelectProperty;
|
||||
import org.saturnclient.mod.Mod;
|
||||
import org.saturnclient.mod.ModSpec;
|
||||
import org.saturnclient.ui.anim.Animation;
|
||||
@@ -18,11 +18,10 @@ public class ZoomMod extends Mod {
|
||||
private static final BoolProperty enabled = Property.bool(false);
|
||||
private static final BoolProperty toggle = Property.bool(false);
|
||||
public static final KeybindingProperty zoomKey = Property.keybinding(GLFWProvider.GLFW_KEY_C);
|
||||
public static final FloatProperty zoomLevel = Property.floatProp(3.0f);
|
||||
public static final IntProperty zoomInDuration = Property.integer(700);
|
||||
public static final IntProperty zoomOutDuration = Property.integer(300);
|
||||
public static final SelectProperty curve = Property.select(1, "Ease In Out Cubic", "Ease Out Cubic",
|
||||
"Ease In Out Back");
|
||||
public static final FloatProperty zoomLevel = Property.floatProp(3.0f).range(1f, 10f, 0.5f).unit("×");
|
||||
public static final IntProperty zoomInDuration = Property.integer(700).range(0, 2000, 50).unit("ms");
|
||||
public static final IntProperty zoomOutDuration = Property.integer(300).range(0, 2000, 50).unit("ms");
|
||||
public static final CurveProperty curve = Property.curve(1);
|
||||
|
||||
public ZoomMod() {
|
||||
super(
|
||||
@@ -31,12 +30,12 @@ public class ZoomMod extends Mod {
|
||||
.tags("Camera", "Utility")
|
||||
.version("v0.2.0"),
|
||||
enabled.named("Enabled"),
|
||||
toggle.named("Toggle zoom"),
|
||||
zoomKey.named("Zoom Keybinding"),
|
||||
zoomLevel.named("Zoom Level"),
|
||||
zoomInDuration.named("Zoom In Duration"),
|
||||
zoomOutDuration.named("Zoom Out Duration"),
|
||||
curve.named("Curve"));
|
||||
toggle.named("Toggle zoom").description("Press the key once instead of holding it"),
|
||||
zoomKey.named("Zoom Keybinding").description("The key that zooms in"),
|
||||
zoomLevel.named("Zoom Level").description("How far the camera zooms in"),
|
||||
zoomInDuration.named("Zoom In Duration").description("How long zooming in takes"),
|
||||
zoomOutDuration.named("Zoom Out Duration").description("How long zooming back out takes"),
|
||||
curve.named("Curve").description("Easing of the zoom"));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
@@ -11,7 +11,6 @@ public class Element {
|
||||
public boolean focused;
|
||||
public Animation animation;
|
||||
public float scale = 1.0f;
|
||||
public Integer duration = null; // Element duration, will fade out if not null
|
||||
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
}
|
||||
|
||||
@@ -1,20 +1,54 @@
|
||||
package org.saturnclient.ui;
|
||||
|
||||
public class ElementContext {
|
||||
/**
|
||||
* Where a container's children are drawn: the screen position of their
|
||||
* (0, 0), and the part of the screen, top to bottom, where they can be seen.
|
||||
*/
|
||||
public record Frame(int x, int y, int visibleTop, int visibleBottom) {
|
||||
/** The whole screen, for top-level elements */
|
||||
public static final Frame SCREEN = new Frame(0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
public int mouseX, mouseY;
|
||||
/** The element's top-left corner on the screen, for placing popups next to it */
|
||||
public int originX, originY;
|
||||
/** The part of the screen, top to bottom, where the element can be seen */
|
||||
public int visibleTop, visibleBottom;
|
||||
public int elementWidth, elementHeight;
|
||||
public float elementScale;
|
||||
public long elapsed;
|
||||
|
||||
public ElementContext(long elapsed, int mouseX, int mouseY, Element e) {
|
||||
public ElementContext(long elapsed, int mouseX, int mouseY, Frame frame, Element e) {
|
||||
this.mouseX = mouseX - e.x;
|
||||
this.mouseY = mouseY - e.y;
|
||||
this.originX = frame.x() + e.x;
|
||||
this.originY = frame.y() + e.y;
|
||||
this.visibleTop = frame.visibleTop();
|
||||
this.visibleBottom = frame.visibleBottom();
|
||||
this.elementWidth = e.width;
|
||||
this.elementHeight = e.height;
|
||||
this.elementScale = e.scale;
|
||||
this.elapsed = elapsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* The frame for this element's children, drawn {@code dx}, {@code dy} from
|
||||
* its corner and seen only between {@code clipTop} and {@code clipBottom}
|
||||
* (relative to its corner).
|
||||
*/
|
||||
public Frame childFrame(int dx, int dy, int clipTop, int clipBottom) {
|
||||
return new Frame(originX + dx, originY + dy,
|
||||
Math.max(visibleTop, originY + clipTop), Math.min(visibleBottom, originY + clipBottom));
|
||||
}
|
||||
|
||||
/**
|
||||
* The frame for children drawn at this element's corner and not clipped.
|
||||
*/
|
||||
public Frame childFrame() {
|
||||
return new Frame(originX, originY, visibleTop, visibleBottom);
|
||||
}
|
||||
|
||||
public boolean isHovering() {
|
||||
return Utils.isHovering(mouseX, mouseY, elementWidth, elementHeight, elementScale);
|
||||
}
|
||||
|
||||
@@ -15,14 +15,27 @@ public class ElementRenderer {
|
||||
}
|
||||
|
||||
public static void render(List<Element> elements, long elapsed, RenderScope renderScope, int mouseX, int mouseY) {
|
||||
render(elements, elapsed, renderScope, mouseX, mouseY, ElementContext.Frame.SCREEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a container's children within its {@code frame}.
|
||||
*/
|
||||
public static void render(List<Element> elements, long elapsed, RenderScope renderScope, int mouseX, int mouseY,
|
||||
ElementContext.Frame frame) {
|
||||
for (Element element : elements) {
|
||||
element.playAnimationFrame(elapsed);
|
||||
|
||||
// Not faded in yet
|
||||
if (element.opacity <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
renderScope.getMatrixStack().push();
|
||||
renderScope.setOpacity(element.opacity);
|
||||
renderScope.getMatrixStack().translate(element.x, element.y);
|
||||
renderScope.getMatrixStack().scale(element.scale, element.scale);
|
||||
element.render(renderScope, new ElementContext(elapsed, mouseX, mouseY, element));
|
||||
element.render(renderScope, new ElementContext(elapsed, mouseX, mouseY, frame, element));
|
||||
renderScope.getMatrixStack().pop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.saturnclient.ui;
|
||||
import org.saturnclient.ui.components.Sidebar;
|
||||
import org.saturnclient.ui.elements.ImageTexture;
|
||||
import org.saturnclient.ui.elements.Panel;
|
||||
import org.saturnclient.ui.elements.Scroll;
|
||||
import org.saturnclient.ui.resources.Textures;
|
||||
|
||||
/**
|
||||
@@ -18,6 +19,9 @@ public abstract class PanelScreen extends SaturnScreen {
|
||||
|
||||
protected static final int PADDING = 20;
|
||||
|
||||
/** How far a scroll reaches past its content area, into the padding, for its bar */
|
||||
private static final int SCROLL_OVERHANG = 14;
|
||||
|
||||
private final int sidebarItem;
|
||||
|
||||
protected int panelX, panelY, panelWidth, panelHeight;
|
||||
@@ -61,4 +65,18 @@ public abstract class PanelScreen extends SaturnScreen {
|
||||
* Draws the panel's content.
|
||||
*/
|
||||
protected abstract void panel();
|
||||
|
||||
/**
|
||||
* The width of a scroll filling {@code width} of the content area.
|
||||
*/
|
||||
protected static int scrollWidth(int width) {
|
||||
return width + SCROLL_OVERHANG;
|
||||
}
|
||||
|
||||
/**
|
||||
* The width left for a scroll's children, clear of its scrollbar.
|
||||
*/
|
||||
protected static int scrollContentWidth(int width) {
|
||||
return scrollWidth(width) - Scroll.SCROLLBAR_SPACE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
package org.saturnclient.ui;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A single element drawn above the rest of the screen, such as a dropdown's
|
||||
* options or a color picker. It gets mouse events before anything else, and a
|
||||
* click or scroll outside it closes it.
|
||||
*
|
||||
* Only one screen is open at a time, so the popup is kept here rather than on
|
||||
* the screen; {@link SaturnScreen} forwards to it and clears it when it's
|
||||
* rebuilt.
|
||||
*/
|
||||
public final class Popup {
|
||||
private static final int SCREEN_MARGIN = 8;
|
||||
private static final int OWNER_GAP = 4;
|
||||
|
||||
private static Element popup;
|
||||
private static Element owner;
|
||||
private static int screenWidth, screenHeight;
|
||||
|
||||
private Popup() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens {@code popup} with its top-left corner at ({@code x}, {@code y}) on
|
||||
* the screen, moved as needed to stay on it.
|
||||
*
|
||||
* @param owner the element that opened it, for {@link #isOpenFor}
|
||||
*/
|
||||
public static void open(Element owner, Element popup, int x, int y) {
|
||||
Popup.owner = owner;
|
||||
Popup.popup = popup;
|
||||
|
||||
popup.x = clamp(x, popup.width, screenWidth);
|
||||
popup.y = clamp(y, popup.height, screenHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens {@code popup} just below its owner if it fits in the part of the
|
||||
* screen where the owner can be seen, such as a scroll's box; otherwise
|
||||
* above it if it fits there, or else on the side with more room.
|
||||
*
|
||||
* @param ownerContext the owner's context from its last render
|
||||
*/
|
||||
public static void openBelow(Element owner, Element popup, ElementContext ownerContext) {
|
||||
openBelow(owner, popup, ownerContext, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #openBelow(Element, Element, ElementContext)}, with the popup
|
||||
* moved {@code offsetX} from the owner's left edge.
|
||||
*/
|
||||
public static void openBelow(Element owner, Element popup, ElementContext ownerContext, int offsetX) {
|
||||
int top = Math.max(ownerContext.visibleTop, SCREEN_MARGIN);
|
||||
int bottom = Math.min(ownerContext.visibleBottom, screenHeight - SCREEN_MARGIN);
|
||||
|
||||
int below = ownerContext.originY + owner.height + OWNER_GAP;
|
||||
int above = ownerContext.originY - OWNER_GAP - popup.height;
|
||||
|
||||
boolean fitsBelow = below + popup.height <= bottom;
|
||||
boolean fitsAbove = above >= top;
|
||||
boolean moreRoomBelow = bottom - below >= ownerContext.originY - OWNER_GAP - top;
|
||||
|
||||
int y = fitsBelow || (!fitsAbove && moreRoomBelow) ? below : above;
|
||||
open(owner, popup, ownerContext.originX + offsetX, y);
|
||||
}
|
||||
|
||||
public static void close() {
|
||||
popup = null;
|
||||
owner = null;
|
||||
}
|
||||
|
||||
public static boolean isOpenFor(Element element) {
|
||||
return popup != null && owner == element;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Called by SaturnScreen
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
static void reset(int width, int height) {
|
||||
close();
|
||||
screenWidth = width;
|
||||
screenHeight = height;
|
||||
}
|
||||
|
||||
static void render(RenderScope renderScope, long elapsed, int mouseX, int mouseY) {
|
||||
if (popup != null) {
|
||||
ElementRenderer.render(List.of(popup), elapsed, renderScope, mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the popup took the click; a click outside it closes it
|
||||
* and is taken too, so it doesn't reach what's underneath
|
||||
*/
|
||||
static boolean mouseClicked(double mouseX, double mouseY, int button) {
|
||||
if (popup == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isInside(mouseX, mouseY)) {
|
||||
ElementRenderer.mouseClicked(List.of(popup), mouseX, mouseY, button);
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
|
||||
if (popup == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ElementRenderer.mouseDragged(List.of(popup), mouseX, mouseY, button, deltaX, deltaY);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void mouseReleased(double mouseX, double mouseY, int button) {
|
||||
if (popup != null) {
|
||||
ElementRenderer.mouseReleased(List.of(popup), mouseX, mouseY, button);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the popup took the scroll; scrolling outside it closes it,
|
||||
* since it wouldn't move with what's underneath
|
||||
*/
|
||||
static boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) {
|
||||
if (popup == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isInside(mouseX, mouseY)) {
|
||||
ElementRenderer.mouseScrolled(List.of(popup), mouseX, mouseY, horizontalAmount, verticalAmount);
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isInside(double mouseX, double mouseY) {
|
||||
return mouseX >= popup.x && mouseX <= popup.x + popup.width
|
||||
&& mouseY >= popup.y && mouseY <= popup.y + popup.height;
|
||||
}
|
||||
|
||||
private static int clamp(int position, int size, int screenSize) {
|
||||
return Math.max(SCREEN_MARGIN, Math.min(position, screenSize - SCREEN_MARGIN - size));
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,14 @@ public interface RenderScope {
|
||||
if (color == 0)
|
||||
return;
|
||||
|
||||
drawTexture(getRoundedRectangle(width, height, radius), x, y, 0, 0, width, height, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* The white rounded rectangle that {@link #drawRoundedRectangle} tints, as a
|
||||
* cached texture.
|
||||
*/
|
||||
default IdentifierRef getRoundedRectangle(int width, int height, int radius) {
|
||||
radius = Math.min(radius, width / 2);
|
||||
radius = Math.min(radius, height / 2);
|
||||
|
||||
@@ -42,7 +50,38 @@ public interface RenderScope {
|
||||
width, height,
|
||||
radius, radius);
|
||||
|
||||
drawSvgShape(rect, "rounded_" + width + "_" + height + "_" + radius, x, y, width, height, color);
|
||||
return getSvgShape(rect, "rounded_" + width + "_" + height + "_" + radius, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Frosts a rounded rectangle for a translucent background to be drawn over:
|
||||
* the game behind the screen, blurred by {@code blur}, cut to exactly the
|
||||
* shape {@link #drawRoundedRectangle} draws. Render scopes without glass
|
||||
* draw nothing.
|
||||
*/
|
||||
default void drawGlass(int x, int y, int width, int height, int radius, int blur) {
|
||||
drawGlass(getRoundedRectangle(width, height, radius), x, y, width, height, blur);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the game behind the screen, blurred by {@code blur}, where
|
||||
* {@code mask}, stretched over the area, is opaque.
|
||||
*/
|
||||
default void drawGlass(IdentifierRef mask, int x, int y, int width, int height, int blur) {
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link #drawRoundedRectangle} with a border, frosted with glass blurred
|
||||
* by {@code blur} when the background isn't solid. A blur of 0 leaves it
|
||||
* plain, showing the screen's own background blur.
|
||||
*/
|
||||
default void drawGlassRectangle(int x, int y, int width, int height, int radius, int blur, int thickness,
|
||||
int background, int border) {
|
||||
if (blur > 0 && background >>> 24 < 0xFF) {
|
||||
drawGlass(x, y, width, height, radius, blur);
|
||||
}
|
||||
|
||||
drawRoundedRectangle(x, y, width, height, radius, thickness, background, border);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,15 +128,22 @@ public interface RenderScope {
|
||||
* {@code color}.
|
||||
*/
|
||||
private void drawSvgShape(String shape, String cacheKey, int x, int y, int width, int height, int color) {
|
||||
drawTexture(getSvgShape(shape, cacheKey, width, height), x, y, 0, 0, width, height, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code shape} (SVG elements in a {@code width} x {@code height} canvas,
|
||||
* drawn in white) rendered to a cached texture.
|
||||
*/
|
||||
private IdentifierRef getSvgShape(String shape, String cacheKey, int width, int height) {
|
||||
String svg = String.format(
|
||||
"<svg xmlns='http://www.w3.org/2000/svg' width='%d' height='%d'>%s</svg>",
|
||||
width, height, shape);
|
||||
|
||||
InputStream svgStream = new ByteArrayInputStream(svg.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
drawTexture(SvgTexture.getSvg(svgStream,
|
||||
IdentifierRef.ofSaturn("textures/gui/tmp_rect/" + cacheKey),
|
||||
width * 4, height * 4), x, y, 0, 0, width, height, color);
|
||||
return SvgTexture.getSvg(svgStream, IdentifierRef.ofSaturn("textures/gui/tmp_rect/" + cacheKey),
|
||||
width * 4, height * 4);
|
||||
}
|
||||
|
||||
void drawBorder(int x, int y, int width, int height, int color);
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
package org.saturnclient.ui;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.saturnclient.config.Config;
|
||||
import org.saturnclient.config.property.FloatProperty;
|
||||
|
||||
public abstract class SaturnScreen {
|
||||
/** How long the background blur takes to fade in, in ms */
|
||||
private static final int BLUR_FADE_IN = 700;
|
||||
|
||||
public static interface ScreenProvider {
|
||||
public void close();
|
||||
|
||||
/** The screen's width in GUI pixels */
|
||||
public int getWidth();
|
||||
|
||||
/** The screen's height in GUI pixels */
|
||||
public int getHeight();
|
||||
}
|
||||
|
||||
@@ -17,12 +26,17 @@ public abstract class SaturnScreen {
|
||||
public int width;
|
||||
public int height;
|
||||
public String title;
|
||||
/** GUI pixels per Saturn unit, from {@link #getScale()} when the screen was built */
|
||||
public float scale = 0.5f;
|
||||
/** Whether a mouse button is held, so the screen isn't rebuilt under a drag */
|
||||
private boolean mouseDown = false;
|
||||
|
||||
// Saturn Screen
|
||||
public List<Element> elements = new ArrayList<>();
|
||||
public float backgroundOpacity = 1.0f;
|
||||
public Instant start = null;
|
||||
public int backgroundBlur = 10;
|
||||
/** Whether the game behind this screen is blurred, by the client's background blur setting */
|
||||
public boolean blurBackground = true;
|
||||
|
||||
// Provider
|
||||
public ScreenProvider provider;
|
||||
@@ -31,10 +45,53 @@ public abstract class SaturnScreen {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
/**
|
||||
* How much to blur the game behind this screen right now: the client's
|
||||
* setting, faded in over the screen's first moments.
|
||||
*/
|
||||
public int getBackgroundBlur() {
|
||||
if (!blurBackground || start == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
long elapsed = Duration.between(start, Instant.now()).toMillis();
|
||||
return Math.round(Config.backgroundBlur.value * Math.min(elapsed / (float) BLUR_FADE_IN, 1f));
|
||||
}
|
||||
|
||||
/**
|
||||
* The GUI scale setting this screen follows; the menu scale unless the
|
||||
* screen is another kind.
|
||||
*/
|
||||
protected FloatProperty getScaleSetting() {
|
||||
return Config.menuScale;
|
||||
}
|
||||
|
||||
/**
|
||||
* GUI pixels per Saturn unit. At 1× a Saturn unit is half a GUI pixel.
|
||||
* Enlarging stops where the screen would be smaller than 320x240 GUI
|
||||
* pixels, the least Minecraft's own GUI scale leaves.
|
||||
*/
|
||||
public float getScale() {
|
||||
float fit = Math.min(provider.getWidth() / 320f, provider.getHeight() / 240f);
|
||||
return 0.5f * Math.min(getScaleSetting().value, Math.max(1f, fit));
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds the screen if its scale setting changed, once no drag is going
|
||||
* on (the slider being dragged would be replaced).
|
||||
*/
|
||||
public void updateScale() {
|
||||
if (!mouseDown && getScale() != scale) {
|
||||
init();
|
||||
}
|
||||
}
|
||||
|
||||
public void init() {
|
||||
elements.clear();
|
||||
width = provider.getWidth();
|
||||
height = provider.getHeight();
|
||||
scale = getScale();
|
||||
width = (int) (provider.getWidth() / scale);
|
||||
height = (int) (provider.getHeight() / scale);
|
||||
Popup.reset(width, height);
|
||||
ui(); // abstraction to render the saturn ui and also render extra stuff here
|
||||
}
|
||||
|
||||
@@ -51,9 +108,10 @@ public abstract class SaturnScreen {
|
||||
public void render(RenderScope renderScope, int mouseX, int mouseY, float delta, long elapsed) {
|
||||
renderScope.getMatrixStack().push();
|
||||
|
||||
renderScope.getMatrixStack().scale(0.5f, 0.5f);
|
||||
renderScope.getMatrixStack().scale(scale, scale);
|
||||
|
||||
ElementRenderer.render(new ArrayList<>(elements), elapsed, renderScope, mouseX, mouseY);
|
||||
Popup.render(renderScope, elapsed, mouseX, mouseY);
|
||||
|
||||
renderScope.getMatrixStack().pop();
|
||||
}
|
||||
@@ -63,27 +121,35 @@ public abstract class SaturnScreen {
|
||||
// --------------
|
||||
|
||||
public boolean mouseClicked(double mouseX, double mouseY, int button) {
|
||||
mouseX *= 2;
|
||||
mouseY *= 2;
|
||||
mouseDown = true;
|
||||
mouseX /= scale;
|
||||
mouseY /= scale;
|
||||
|
||||
ElementRenderer.mouseClicked(elements, mouseX, mouseY, button);
|
||||
if (!Popup.mouseClicked(mouseX, mouseY, button)) {
|
||||
ElementRenderer.mouseClicked(elements, mouseX, mouseY, button);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
|
||||
mouseX *= 2;
|
||||
mouseY *= 2;
|
||||
mouseX /= scale;
|
||||
mouseY /= scale;
|
||||
|
||||
ElementRenderer.mouseDragged(elements, mouseX, mouseY, button, deltaX, deltaY);
|
||||
if (!Popup.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)) {
|
||||
ElementRenderer.mouseDragged(elements, mouseX, mouseY, button, deltaX, deltaY);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean mouseReleased(double mouseX, double mouseY, int button) {
|
||||
mouseX *= 2;
|
||||
mouseY *= 2;
|
||||
mouseDown = false;
|
||||
mouseX /= scale;
|
||||
mouseY /= scale;
|
||||
|
||||
// Everything hears the release, so no element is left thinking it's being dragged.
|
||||
Popup.mouseReleased(mouseX, mouseY, button);
|
||||
ElementRenderer.mouseReleased(elements, mouseX, mouseY, button);
|
||||
|
||||
return false;
|
||||
@@ -94,10 +160,12 @@ public abstract class SaturnScreen {
|
||||
double mouseY,
|
||||
double horizontalAmount,
|
||||
double verticalAmount) {
|
||||
mouseX *= 2;
|
||||
mouseY *= 2;
|
||||
mouseX /= scale;
|
||||
mouseY /= scale;
|
||||
|
||||
ElementRenderer.mouseScrolled(elements, mouseX, mouseY, horizontalAmount, verticalAmount);
|
||||
if (!Popup.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount)) {
|
||||
ElementRenderer.mouseScrolled(elements, mouseX, mouseY, horizontalAmount, verticalAmount);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.saturnclient.ui;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.saturnclient.common.provider.GLFWProvider;
|
||||
|
||||
public class Utils {
|
||||
@@ -47,17 +45,21 @@ public class Utils {
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> T getOrNull(List<T> list, int index) {
|
||||
return (index >= 0 && index < list.size()) ? list.get(index) : null;
|
||||
/**
|
||||
* A display name from an id: each underscore-separated word capitalized, so
|
||||
* "black_hole_white" becomes "Black Hole White".
|
||||
*/
|
||||
public static String nameFromId(String id) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (String word : id.split("_")) {
|
||||
if (word.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (!builder.isEmpty()) {
|
||||
builder.append(' ');
|
||||
}
|
||||
builder.append(Character.toUpperCase(word.charAt(0))).append(word.substring(1));
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
// public static void notify(NotificationKind kind, String title, String toast)
|
||||
// {
|
||||
// if (SaturnClient.client.currentScreen instanceof SaturnScreen) {
|
||||
// ((SaturnScreen) SaturnClient.client.currentScreen)
|
||||
// .draw(new Notification(SaturnClient.client.currentScreen.width,
|
||||
// SaturnClient.client.currentScreen.height, kind,
|
||||
// title, toast));
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -16,12 +16,12 @@ public class Fade extends Animation {
|
||||
|
||||
@Override
|
||||
public void tick(double progress, Element element) {
|
||||
element.opacity = Math.max(0.1f, targetOpacity * (float) progress);
|
||||
element.opacity = targetOpacity * (float) progress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Element element) {
|
||||
targetOpacity = element.opacity;
|
||||
element.opacity = 0.1f;
|
||||
element.opacity = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package org.saturnclient.ui.anim;
|
||||
|
||||
import org.saturnclient.config.AnimationConfig;
|
||||
import org.saturnclient.ui.Element;
|
||||
|
||||
public class FadeOut extends Animation {
|
||||
float targetOpacity;
|
||||
|
||||
public FadeOut(int duration) {
|
||||
super(duration);
|
||||
}
|
||||
|
||||
public FadeOut(AnimationConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(double progress, Element element) {
|
||||
element.opacity = targetOpacity - (float) progress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Element element) {
|
||||
targetOpacity = element.opacity;
|
||||
// element.opacity = 1.0f;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package org.saturnclient.ui.anim;
|
||||
|
||||
import org.saturnclient.config.AnimationConfig;
|
||||
import org.saturnclient.ui.Element;
|
||||
|
||||
public class SlideX extends Animation {
|
||||
int offset = 20;
|
||||
double startX;
|
||||
|
||||
public SlideX(AnimationConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
public SlideX(AnimationConfig config, int offset) {
|
||||
super(config);
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public SlideX(int duration) {
|
||||
super(duration);
|
||||
}
|
||||
|
||||
public SlideX(int duration, int offset) {
|
||||
super(duration);
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Element element) {
|
||||
startX = element.x - offset;
|
||||
element.x = (int) startX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(double progress, Element element) {
|
||||
double currentX = startX + offset * progress;
|
||||
element.x = (int) currentX;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.saturnclient.ui.components;
|
||||
|
||||
import org.saturnclient.client.ServiceClient;
|
||||
import org.saturnclient.common.provider.Providers;
|
||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||
import org.saturnclient.config.Config;
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.elements.Field;
|
||||
import org.saturnclient.ui.resources.Fonts;
|
||||
|
||||
/**
|
||||
* The signed-in player: their skin's face, their name, and a dot that's green
|
||||
* while connected to Saturn.
|
||||
*/
|
||||
public class AccountChip extends Element {
|
||||
public static final int HEIGHT = 32;
|
||||
|
||||
private static final int HEAD = 18;
|
||||
private static final int PADDING = 7;
|
||||
private static final int GAP = 8;
|
||||
private static final int DOT = 6;
|
||||
private static final float NAME_SCALE = 0.55f;
|
||||
private static final int CONNECTED = 0xFF7FD1A8;
|
||||
|
||||
private final String name;
|
||||
|
||||
public AccountChip() {
|
||||
String username = Providers.saturn.getClient().getUsername();
|
||||
this.name = username != null ? username : "Player";
|
||||
this.height = HEIGHT;
|
||||
this.width = PADDING + HEAD + GAP + (int) (Fonts.getWidth(name, Theme.FONT.value) * NAME_SCALE) + GAP + DOT
|
||||
+ GAP + 2;
|
||||
}
|
||||
|
||||
@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());
|
||||
|
||||
// The face, then the hat layer over it, from the 64x64 skin
|
||||
IdentifierRef skin = Providers.saturn.getPlayerSkin();
|
||||
int headY = (height - HEAD) / 2;
|
||||
renderScope.drawTexture(skin, PADDING, headY, 8, 8, HEAD, HEAD, 8, 8, 64, 64);
|
||||
renderScope.drawTexture(skin, PADDING, headY, 40, 8, HEAD, HEAD, 8, 8, 64, 64);
|
||||
|
||||
int nameX = PADDING + HEAD + GAP;
|
||||
renderScope.drawText(NAME_SCALE, name, nameX, Field.textY(height, NAME_SCALE), Theme.FONT.value,
|
||||
Theme.FOREGROUND.value);
|
||||
|
||||
int dotX = nameX + (int) (Fonts.getWidth(name, Theme.FONT.value) * NAME_SCALE) + GAP;
|
||||
renderScope.drawRoundedRectangle(dotX, (height - DOT) / 2, DOT, DOT, DOT / 2,
|
||||
ServiceClient.isConnected() ? CONNECTED : Theme.withAlpha(0.3f, Theme.FOREGROUND.value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package org.saturnclient.ui.components;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.config.property.ColorProperty;
|
||||
import org.saturnclient.config.property.Property;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.components.inputs.ColorInput;
|
||||
import org.saturnclient.ui.elements.Scroll;
|
||||
import org.saturnclient.ui.resources.Fonts;
|
||||
|
||||
/**
|
||||
* Lays out the color properties of a config as a two-column grid of tiles,
|
||||
* each with the color's name and its input.
|
||||
*/
|
||||
public final class ColorGrid {
|
||||
private static final int COLUMNS = 2;
|
||||
private static final int TILE_HEIGHT = 48;
|
||||
private static final int GAP = 8;
|
||||
private static final int INSET = 8;
|
||||
|
||||
private ColorGrid() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a tile for every {@link ColorProperty} in {@code properties} to
|
||||
* {@code scroll}, starting at {@code y}.
|
||||
*
|
||||
* @return the y below the grid
|
||||
*/
|
||||
public static int draw(Scroll scroll, Map<String, Property> properties, int width, int y) {
|
||||
int tileWidth = (width - GAP * (COLUMNS - 1)) / COLUMNS;
|
||||
int i = 0;
|
||||
|
||||
for (Map.Entry<String, Property> entry : properties.entrySet()) {
|
||||
if (!(entry.getValue() instanceof ColorProperty color)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int x = (i % COLUMNS) * (tileWidth + GAP);
|
||||
int tileY = y + (i / COLUMNS) * (TILE_HEIGHT + GAP);
|
||||
|
||||
ColorInput input = new ColorInput(color);
|
||||
scroll.draw(new Tile(entry.getKey(), tileWidth, input.width).position(x, tileY));
|
||||
scroll.draw(input.position(x + tileWidth - INSET - input.width, tileY + (TILE_HEIGHT - input.height) / 2));
|
||||
i++;
|
||||
}
|
||||
|
||||
int rows = (i + COLUMNS - 1) / COLUMNS;
|
||||
return y + rows * TILE_HEIGHT + Math.max(0, rows - 1) * GAP;
|
||||
}
|
||||
|
||||
private static class Tile extends Element {
|
||||
private static final float NAME_SCALE = 0.7f;
|
||||
|
||||
private final String name;
|
||||
private final int inputWidth;
|
||||
|
||||
Tile(String name, int width, int inputWidth) {
|
||||
this.name = name;
|
||||
this.inputWidth = inputWidth;
|
||||
this.width = width;
|
||||
this.height = TILE_HEIGHT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, 1,
|
||||
Theme.withAlpha(0.72f, Theme.PRIMARY.value), Theme.withAlpha(0.06f, Theme.FOREGROUND.value));
|
||||
|
||||
String text = Fonts.ellipsize(name, Theme.FONT.value, NAME_SCALE, width - inputWidth - INSET * 2 - 12);
|
||||
int nameY = (height - (int) (Fonts.getHeight() * NAME_SCALE)) / 2;
|
||||
renderScope.drawText(NAME_SCALE, text, 12, nameY, Theme.FONT.value, Theme.FOREGROUND.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package org.saturnclient.ui.components;
|
||||
|
||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
|
||||
public class CosmeticPreview extends Element {
|
||||
static int padding = 14;
|
||||
|
||||
IdentifierRef sprite;
|
||||
private Runnable onClick;
|
||||
private boolean isSelected;
|
||||
|
||||
public CosmeticPreview(boolean isSelected, IdentifierRef sprite, Runnable onClick) {
|
||||
this.sprite = sprite;
|
||||
this.onClick = onClick;
|
||||
this.isSelected = isSelected;
|
||||
|
||||
this.width = 50;
|
||||
this.height = 111;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value,
|
||||
isSelected ? Theme.ACCENT.value : Theme.PRIMARY.value);
|
||||
|
||||
int texWidth = width - padding;
|
||||
int texHeight = height - padding;
|
||||
|
||||
renderScope.drawTexture(sprite, padding / 2, padding / 2, 0, 0, texWidth, texHeight,
|
||||
Theme.FOREGROUND.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(int mouseX, int mouseY) {
|
||||
if (onClick != null) {
|
||||
onClick.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package org.saturnclient.ui.components;
|
||||
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.resources.Fonts;
|
||||
import org.saturnclient.ui.resources.Textures;
|
||||
|
||||
/**
|
||||
* One cosmetic in the cosmetics grid: its preview and name. It shows whether
|
||||
* it's equipped, and cosmetics not owned yet are dimmed with a "Get" label.
|
||||
*/
|
||||
public class CosmeticTile extends Element {
|
||||
public static final int HEIGHT = 168;
|
||||
|
||||
private static final int INSET = 8;
|
||||
private static final int IMAGE_AREA = 124;
|
||||
private static final float NAME_SCALE = 0.62f;
|
||||
private static final float BADGE_SCALE = 0.54f;
|
||||
private static final int BADGE_HEIGHT = 20;
|
||||
/** How long a claim may take before it can be tried again, in ms */
|
||||
private static final long CLAIM_TIMEOUT = 5000;
|
||||
|
||||
private final String name;
|
||||
private final IdentifierRef preview;
|
||||
private final float previewAspect;
|
||||
private final BooleanSupplier owned;
|
||||
private final BooleanSupplier equipped;
|
||||
private final Runnable onEquip;
|
||||
private final Runnable onClaim;
|
||||
|
||||
private long claimedAt = 0;
|
||||
|
||||
/**
|
||||
* @param previewAspect the preview texture's width divided by its height
|
||||
*/
|
||||
public CosmeticTile(String name, IdentifierRef preview, float previewAspect, int width, BooleanSupplier owned,
|
||||
BooleanSupplier equipped, Runnable onEquip, Runnable onClaim) {
|
||||
this.name = name;
|
||||
this.preview = preview;
|
||||
this.previewAspect = previewAspect;
|
||||
this.owned = owned;
|
||||
this.equipped = equipped;
|
||||
this.onEquip = onEquip;
|
||||
this.onClaim = onClaim;
|
||||
this.width = width;
|
||||
this.height = HEIGHT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
boolean isOwned = owned.getAsBoolean();
|
||||
boolean isEquipped = equipped.getAsBoolean();
|
||||
boolean hovered = ctx.isHovering();
|
||||
|
||||
int border = isEquipped ? Theme.ACCENT.value
|
||||
: Theme.withAlpha(hovered ? 0.22f : 0.06f, Theme.FOREGROUND.value);
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, isEquipped ? 2 : 1,
|
||||
Theme.PRIMARY.value, border);
|
||||
|
||||
// Preview, fitted into its area
|
||||
int areaWidth = width - INSET * 2;
|
||||
renderScope.drawRoundedRectangle(INSET, INSET, areaWidth, IMAGE_AREA, Theme.WIDGET_RADIUS.value - 2,
|
||||
Theme.withAlpha(0.05f, Theme.FOREGROUND.value));
|
||||
|
||||
int imageHeight = IMAGE_AREA - 12;
|
||||
int imageWidth = (int) (imageHeight * previewAspect);
|
||||
if (imageWidth > areaWidth - 12) {
|
||||
imageWidth = areaWidth - 12;
|
||||
imageHeight = (int) (imageWidth / previewAspect);
|
||||
}
|
||||
|
||||
renderScope.drawTexture(preview, INSET + (areaWidth - imageWidth) / 2, INSET + (IMAGE_AREA - imageHeight) / 2,
|
||||
0, 0, imageWidth, imageHeight, isOwned ? 0xFFFFFFFF : Theme.withAlpha(0.35f, 0xFFFFFFFF));
|
||||
|
||||
// Name
|
||||
int nameY = INSET + IMAGE_AREA + (HEIGHT - INSET - IMAGE_AREA - (int) (Fonts.getHeight() * NAME_SCALE)) / 2;
|
||||
renderScope.drawText(NAME_SCALE, Fonts.ellipsize(name, Theme.FONT.value, NAME_SCALE, width - 24), 12, nameY,
|
||||
Theme.FONT.value, isOwned ? Theme.FOREGROUND.value : Theme.getMutedFg());
|
||||
|
||||
// Badge in the preview's corner
|
||||
if (isEquipped) {
|
||||
drawBadge(renderScope, "On", Theme.ACCENT.value, Theme.ACCENT_FG.value, true);
|
||||
} else if (!isOwned) {
|
||||
drawBadge(renderScope, isClaiming() ? "Claiming" : "Get", hovered ? Theme.ACCENT.value : Theme.PRIMARY.value,
|
||||
hovered ? Theme.ACCENT_FG.value : Theme.PRIMARY_FG.value, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawBadge(RenderScope renderScope, String text, int background, int foreground, boolean check) {
|
||||
int icon = check ? 10 : 0;
|
||||
int badgeWidth = 14 + icon + (check ? 4 : 0) + (int) (Fonts.getWidth(text, Theme.FONT.value) * BADGE_SCALE);
|
||||
int x = width - INSET - 6 - badgeWidth;
|
||||
int y = INSET + 6;
|
||||
|
||||
renderScope.drawRoundedRectangle(x, y, badgeWidth, BADGE_HEIGHT, 6, background);
|
||||
|
||||
if (check) {
|
||||
renderScope.drawTexture(Textures.ICON_CHECK, x + 7, y + (BADGE_HEIGHT - icon) / 2, 0, 0, icon, icon, foreground);
|
||||
}
|
||||
|
||||
int textY = y + (BADGE_HEIGHT - (int) (Fonts.getHeight() * BADGE_SCALE)) / 2;
|
||||
renderScope.drawText(BADGE_SCALE, text, x + 7 + icon + (check ? 4 : 0), textY, Theme.FONT.value, foreground);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(int mouseX, int mouseY) {
|
||||
if (owned.getAsBoolean()) {
|
||||
if (!equipped.getAsBoolean()) {
|
||||
onEquip.run();
|
||||
}
|
||||
} else if (!isClaiming()) {
|
||||
claimedAt = System.currentTimeMillis();
|
||||
onClaim.run();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isClaiming() {
|
||||
return System.currentTimeMillis() - claimedAt < CLAIM_TIMEOUT;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.saturnclient.ui.components;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||
import org.saturnclient.config.Config;
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
|
||||
/**
|
||||
* A glass bar of square icon buttons.
|
||||
*/
|
||||
public class Dock extends Element {
|
||||
public record Item(IdentifierRef icon, Runnable onClick) {
|
||||
}
|
||||
|
||||
public static final int HEIGHT = 40;
|
||||
|
||||
private static final int PADDING = 5;
|
||||
private static final int ITEM = HEIGHT - PADDING * 2;
|
||||
private static final int ICON = 15;
|
||||
private static final int GAP = 3;
|
||||
|
||||
private final List<Item> items;
|
||||
|
||||
public Dock(List<Item> items) {
|
||||
this.items = items;
|
||||
this.height = HEIGHT;
|
||||
this.width = PADDING * 2 + items.size() * ITEM + (items.size() - 1) * GAP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawGlassRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value + 4, Config.widgetBlur.value, 1,
|
||||
Theme.getPanelBg(),
|
||||
Theme.withAlpha(0.1f, Theme.FOREGROUND.value));
|
||||
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
int x = getItemX(i);
|
||||
boolean hovered = ctx.isHovering(x, PADDING, ITEM, ITEM);
|
||||
|
||||
if (hovered) {
|
||||
renderScope.drawRoundedRectangle(x, PADDING, ITEM, ITEM, Theme.WIDGET_RADIUS.value,
|
||||
Theme.withAlpha(0.08f, Theme.FOREGROUND.value));
|
||||
}
|
||||
|
||||
int offset = (ITEM - ICON) / 2;
|
||||
renderScope.drawTexture(items.get(i).icon(), x + offset, PADDING + offset, 0, 0, ICON, ICON,
|
||||
hovered ? Theme.ACCENT.value : Theme.PRIMARY_FG.value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(int mouseX, int mouseY) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
int x = getItemX(i);
|
||||
if (mouseX >= x && mouseX <= x + ITEM) {
|
||||
items.get(i).onClick().run();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int getItemX(int index) {
|
||||
return PADDING + index * (ITEM + GAP);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package org.saturnclient.ui.components;
|
||||
|
||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||
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.resources.Fonts;
|
||||
import org.saturnclient.ui.resources.Textures;
|
||||
|
||||
/**
|
||||
* One emote in the emote wheel: its preview in its own colors and its name.
|
||||
* An emote without a preview shows an icon instead.
|
||||
*/
|
||||
public class EmoteTile extends Element {
|
||||
public static final int SIZE = 86;
|
||||
|
||||
private static final int INSET = 6;
|
||||
private static final int NAME_AREA = 20;
|
||||
private static final int PLACEHOLDER = 26;
|
||||
private static final float NAME_SCALE = 0.56f;
|
||||
|
||||
private final String name;
|
||||
/** The preview, or null when the emote has none */
|
||||
private final IdentifierRef preview;
|
||||
private final Runnable onPlay;
|
||||
|
||||
public EmoteTile(String emote, Runnable onPlay) {
|
||||
IdentifierRef texture = Textures.getEmotePreview(emote);
|
||||
|
||||
this.name = Emotes.getName(emote);
|
||||
this.preview = Textures.exists(texture) ? texture : null;
|
||||
this.onPlay = onPlay;
|
||||
this.width = SIZE;
|
||||
this.height = SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
boolean hovered = ctx.isHovering();
|
||||
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, hovered ? 2 : 1,
|
||||
Theme.PRIMARY.value,
|
||||
hovered ? Theme.ACCENT.value : Theme.withAlpha(0.06f, Theme.FOREGROUND.value));
|
||||
|
||||
int imageSize = height - INSET * 2 - NAME_AREA;
|
||||
|
||||
if (preview != null) {
|
||||
renderScope.drawTexture(preview, (width - imageSize) / 2, INSET, 0, 0, imageSize, imageSize);
|
||||
} else {
|
||||
int iconY = INSET + (imageSize - PLACEHOLDER) / 2;
|
||||
renderScope.drawTexture(Textures.ICON_EMOTE, (width - PLACEHOLDER) / 2, iconY, 0, 0, PLACEHOLDER,
|
||||
PLACEHOLDER, Theme.getMutedFg());
|
||||
}
|
||||
|
||||
String text = Fonts.ellipsize(name, Theme.FONT.value, NAME_SCALE, width - INSET * 2);
|
||||
int textX = (width - (int) (Fonts.getWidth(text, Theme.FONT.value) * NAME_SCALE)) / 2;
|
||||
int textY = height - INSET - NAME_AREA + (NAME_AREA - (int) (Fonts.getHeight() * NAME_SCALE)) / 2;
|
||||
renderScope.drawText(NAME_SCALE, text, textX, textY, Theme.FONT.value,
|
||||
hovered ? Theme.FOREGROUND.value : Theme.PRIMARY_FG.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(int mouseX, int mouseY) {
|
||||
onPlay.run();
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,21 @@ public class HudModPreview extends Element {
|
||||
|
||||
@Override
|
||||
public void render(RenderScope scope, ElementContext ctx) {
|
||||
ModLayout layout = feature.getDimensions();
|
||||
draw(scope, feature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a HUD mod with sample content and the background it has in game,
|
||||
* at the origin and its layout's unscaled size.
|
||||
*/
|
||||
public static void draw(RenderScope scope, HudMod mod) {
|
||||
ModLayout layout = mod.getDimensions();
|
||||
|
||||
if (layout.renderBackground) {
|
||||
scope.drawRoundedRectangle(0, 0, layout.width, layout.height,
|
||||
layout.radius.value, layout.bgColor.value);
|
||||
}
|
||||
|
||||
feature.renderDummy(scope);
|
||||
mod.renderDummy(scope);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.saturnclient.ui.components;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -23,6 +21,7 @@ import org.saturnclient.ui.components.inputs.FloatInput;
|
||||
import org.saturnclient.ui.components.inputs.IntInput;
|
||||
import org.saturnclient.ui.components.inputs.KeybindingSelector;
|
||||
import org.saturnclient.ui.components.inputs.Select;
|
||||
import org.saturnclient.ui.components.inputs.Slider;
|
||||
import org.saturnclient.ui.components.inputs.Toggle;
|
||||
import org.saturnclient.ui.elements.Label;
|
||||
import org.saturnclient.ui.elements.Scroll;
|
||||
@@ -48,15 +47,6 @@ public final class PropertyList {
|
||||
* @return the y below the last row
|
||||
*/
|
||||
public static int draw(Scroll scroll, Map<String, Property> properties, Set<String> skip, int width, int y) {
|
||||
// Controls go on after every row, so an open color picker covers the rows below it.
|
||||
List<Element> controls = new ArrayList<>();
|
||||
y = drawRows(scroll, properties, skip, width, y, controls);
|
||||
controls.forEach(scroll::draw);
|
||||
return y;
|
||||
}
|
||||
|
||||
private static int drawRows(Scroll scroll, Map<String, Property> properties, Set<String> skip, int width, int y,
|
||||
List<Element> controls) {
|
||||
for (Map.Entry<String, Property> entry : properties.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
Property prop = entry.getValue();
|
||||
@@ -66,12 +56,8 @@ public final class PropertyList {
|
||||
}
|
||||
|
||||
if (prop instanceof NamespaceProperty namespace) {
|
||||
y += SECTION_GAP;
|
||||
String title = name.toUpperCase(Locale.ROOT);
|
||||
scroll.draw(new Label(() -> title, SECTION_SCALE, () -> Theme.FONT.value, Theme::getMutedFg)
|
||||
.position(0, y));
|
||||
y += (int) (Fonts.getHeight() * SECTION_SCALE) + 4;
|
||||
y = drawRows(scroll, namespace.value, Set.of(), width, y, controls);
|
||||
y = drawSection(scroll, name, y + SECTION_GAP) + 4;
|
||||
y = draw(scroll, namespace.value, Set.of(), width, y);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -80,14 +66,25 @@ public final class PropertyList {
|
||||
continue;
|
||||
}
|
||||
|
||||
scroll.draw(new Row(name, width, control.width).position(0, y));
|
||||
controls.add(control.position(width - control.width, y + (ROW_HEIGHT - control.height) / 2));
|
||||
scroll.draw(new Row(name, prop.description, width, control.width).position(0, y));
|
||||
scroll.draw(control.position(width - control.width, y + (ROW_HEIGHT - control.height) / 2));
|
||||
y += ROW_HEIGHT;
|
||||
}
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a section title, such as a namespace's name, at {@code y}.
|
||||
*
|
||||
* @return the y below the title
|
||||
*/
|
||||
public static int drawSection(Scroll scroll, String name, int y) {
|
||||
String title = name.toUpperCase(Locale.ROOT);
|
||||
scroll.draw(new Label(() -> title, SECTION_SCALE, () -> Theme.FONT.value, Theme::getMutedFg).position(0, y));
|
||||
return y + (int) (Fonts.getHeight() * SECTION_SCALE);
|
||||
}
|
||||
|
||||
/**
|
||||
* The input for a property, or null for types without one (strings).
|
||||
*/
|
||||
@@ -97,9 +94,9 @@ public final class PropertyList {
|
||||
} else if (prop instanceof ColorProperty p) {
|
||||
return new ColorInput(p);
|
||||
} else if (prop instanceof IntProperty p) {
|
||||
return new IntInput(p);
|
||||
return p.hasRange() ? Slider.of(p) : new IntInput(p);
|
||||
} else if (prop instanceof FloatProperty p) {
|
||||
return new FloatInput(p);
|
||||
return p.hasRange() ? Slider.of(p) : new FloatInput(p);
|
||||
} else if (prop instanceof SelectProperty p) {
|
||||
return new Select(p);
|
||||
} else if (prop instanceof KeybindingProperty p) {
|
||||
@@ -110,17 +107,22 @@ public final class PropertyList {
|
||||
}
|
||||
|
||||
/**
|
||||
* A row's name and the line under it. The control is a separate element on
|
||||
* top, so it gets the clicks and key presses.
|
||||
* A row's name, its description under it, and the line below the row. The
|
||||
* control is a separate element on top, so it gets the clicks and key
|
||||
* presses.
|
||||
*/
|
||||
private static class Row extends Element {
|
||||
private static final float NAME_SCALE = 0.74f;
|
||||
private static final float DESCRIPTION_SCALE = 0.62f;
|
||||
private static final int LINE_GAP = 4;
|
||||
|
||||
private final String name;
|
||||
private final String description;
|
||||
private final int controlWidth;
|
||||
|
||||
Row(String name, int width, int controlWidth) {
|
||||
Row(String name, String description, int width, int controlWidth) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.controlWidth = controlWidth;
|
||||
this.width = width;
|
||||
this.height = ROW_HEIGHT;
|
||||
@@ -128,10 +130,24 @@ public final class PropertyList {
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
int nameY = (height - (int) (Fonts.getHeight() * NAME_SCALE)) / 2;
|
||||
String text = Fonts.ellipsize(name, Theme.FONT.value, NAME_SCALE, width - controlWidth - 16);
|
||||
int textWidth = width - controlWidth - 16;
|
||||
int nameHeight = (int) (Fonts.getHeight() * NAME_SCALE);
|
||||
int descriptionHeight = (int) (Fonts.getHeight() * DESCRIPTION_SCALE);
|
||||
boolean described = !description.isEmpty();
|
||||
|
||||
int blockHeight = described ? nameHeight + LINE_GAP + descriptionHeight : nameHeight;
|
||||
int nameY = (height - blockHeight) / 2;
|
||||
|
||||
renderScope.drawText(NAME_SCALE, Fonts.ellipsize(name, Theme.FONT.value, NAME_SCALE, textWidth),
|
||||
0, nameY, Theme.FONT.value, Theme.FOREGROUND.value);
|
||||
|
||||
if (described) {
|
||||
int font = Theme.getBodyFont();
|
||||
renderScope.drawText(DESCRIPTION_SCALE,
|
||||
Fonts.ellipsize(description, font, DESCRIPTION_SCALE, textWidth),
|
||||
0, nameY + nameHeight + LINE_GAP, font, Theme.getMutedFg());
|
||||
}
|
||||
|
||||
renderScope.drawText(NAME_SCALE, text, 0, nameY, Theme.FONT.value, Theme.FOREGROUND.value);
|
||||
renderScope.drawRectangle(0, height - 1, width, 1, Theme.withAlpha(0.06f, Theme.FOREGROUND.value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ public class SaturnModule extends Element {
|
||||
|
||||
// Card
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, 1,
|
||||
Theme.withAlpha(0.72f, Theme.PRIMARY.value), border);
|
||||
Theme.PRIMARY.value, border);
|
||||
|
||||
if (enabled) {
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value,
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package org.saturnclient.ui.components;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.elements.Field;
|
||||
import org.saturnclient.ui.resources.Fonts;
|
||||
|
||||
/**
|
||||
* A vertical list of a screen's sections under a small heading, with the
|
||||
* current one highlighted.
|
||||
*/
|
||||
public class SectionNav extends Element {
|
||||
public record Item(IdentifierRef icon, String name) {
|
||||
}
|
||||
|
||||
private static final float HEADING_SCALE = 0.6f;
|
||||
private static final int HEADING_HEIGHT = 26;
|
||||
private static final int ITEM_HEIGHT = 40;
|
||||
private static final int ITEM_GAP = 4;
|
||||
private static final int ICON = 17;
|
||||
private static final float TEXT_SCALE = 0.7f;
|
||||
|
||||
private final String heading;
|
||||
private final List<Item> items;
|
||||
private final int active;
|
||||
private final IntConsumer onSelect;
|
||||
|
||||
public SectionNav(String heading, List<Item> items, int active, int width, IntConsumer onSelect) {
|
||||
this.heading = heading;
|
||||
this.items = items;
|
||||
this.active = active;
|
||||
this.onSelect = onSelect;
|
||||
this.width = width;
|
||||
this.height = HEADING_HEIGHT + items.size() * (ITEM_HEIGHT + ITEM_GAP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawText(HEADING_SCALE, heading, 10, 0, Theme.FONT.value, Theme.getMutedFg());
|
||||
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
Item item = items.get(i);
|
||||
int y = getItemY(i);
|
||||
boolean selected = i == active;
|
||||
boolean hovered = ctx.isHovering(0, y, width, ITEM_HEIGHT);
|
||||
|
||||
if (selected || hovered) {
|
||||
renderScope.drawRoundedRectangle(0, y, width, ITEM_HEIGHT, Theme.WIDGET_RADIUS.value,
|
||||
Theme.withAlpha(selected ? 0.08f : 0.04f, Theme.FOREGROUND.value));
|
||||
}
|
||||
|
||||
renderScope.drawTexture(item.icon(), 12, y + (ITEM_HEIGHT - ICON) / 2, 0, 0, ICON, ICON,
|
||||
selected ? Theme.ACCENT.value : Theme.getMutedFg());
|
||||
|
||||
String name = Fonts.ellipsize(item.name(), Theme.FONT.value, TEXT_SCALE, width - 12 - ICON - 10 - 8);
|
||||
renderScope.drawText(TEXT_SCALE, name, 12 + ICON + 10, y + Field.textY(ITEM_HEIGHT, TEXT_SCALE),
|
||||
Theme.FONT.value, selected ? Theme.FOREGROUND.value : Theme.PRIMARY_FG.value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(int mouseX, int mouseY) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
int y = getItemY(i);
|
||||
|
||||
if (mouseY >= y && mouseY <= y + ITEM_HEIGHT) {
|
||||
if (i != active) {
|
||||
onSelect.accept(i);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getItemY(int index) {
|
||||
return HEADING_HEIGHT + index * (ITEM_HEIGHT + ITEM_GAP);
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,16 @@ package org.saturnclient.ui.components;
|
||||
|
||||
import org.saturnclient.common.provider.Providers;
|
||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||
import org.saturnclient.config.Config;
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.elements.Panel;
|
||||
import org.saturnclient.ui.resources.Textures;
|
||||
import org.saturnclient.ui.screens.ConfigEditor;
|
||||
import org.saturnclient.ui.screens.ClientSettings;
|
||||
import org.saturnclient.ui.screens.HudEditor;
|
||||
import org.saturnclient.ui.screens.ModMenu;
|
||||
import org.saturnclient.ui.screens.cosmetics.CloakMenu;
|
||||
import org.saturnclient.ui.screens.CosmeticsMenu;
|
||||
|
||||
/**
|
||||
* The navigation rail beside Saturn's screens: the logo, one button per screen,
|
||||
@@ -23,9 +22,9 @@ public class Sidebar extends Element {
|
||||
|
||||
// Values for the constructor's `active`
|
||||
public static final int MODS = 0;
|
||||
public static final int COSMETICS = 1;
|
||||
public static final int HUD_EDITOR = 2;
|
||||
public static final int SETTINGS = 3;
|
||||
public static final int SETTINGS = 1;
|
||||
public static final int COSMETICS = 2;
|
||||
public static final int HUD_EDITOR = 3;
|
||||
|
||||
private static final int LOGO = 32;
|
||||
private static final int BUTTON = 44;
|
||||
@@ -36,12 +35,12 @@ public class Sidebar extends Element {
|
||||
private record Item(IdentifierRef icon, Runnable onClick) {
|
||||
}
|
||||
|
||||
// In the order of the constants above
|
||||
private final Item[] items = {
|
||||
new Item(Textures.ICON_MODS, () -> Providers.saturn.getClient().setScreen(new ModMenu())),
|
||||
new Item(Textures.ICON_COSMETICS, () -> Providers.saturn.getClient().setScreen(new CloakMenu())),
|
||||
new Item(Textures.ICON_SETTINGS, () -> Providers.saturn.getClient().setScreen(new ClientSettings())),
|
||||
new Item(Textures.ICON_COSMETICS, () -> Providers.saturn.getClient().setScreen(new CosmeticsMenu())),
|
||||
new Item(Textures.ICON_HUD_EDITOR, () -> Providers.saturn.getClient().setScreen(new HudEditor())),
|
||||
new Item(Textures.ICON_SETTINGS,
|
||||
() -> Providers.saturn.getClient().setScreen(new ConfigEditor(Config.config))),
|
||||
};
|
||||
|
||||
private final int active;
|
||||
|
||||
@@ -1,26 +1,68 @@
|
||||
package org.saturnclient.ui.components;
|
||||
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.resources.Fonts;
|
||||
|
||||
/**
|
||||
* The player, wearing their cosmetics, filling the element. Dragging it turns
|
||||
* the player around.
|
||||
*/
|
||||
public class SkinPreview extends Element {
|
||||
public static EntityDrawer DRAWER;
|
||||
private boolean negativeAngle = false;
|
||||
private float angle = 0.0f;
|
||||
|
||||
public SkinPreview(float angle, boolean negativeAngle) {
|
||||
this.angle = angle;
|
||||
this.negativeAngle = negativeAngle;
|
||||
/** Degrees turned per GUI pixel dragged */
|
||||
private static final float DRAG_SPEED = 1.2f;
|
||||
private static final float HINT_SCALE = 0.66f;
|
||||
|
||||
private float yaw;
|
||||
private boolean dragging = false;
|
||||
|
||||
/**
|
||||
* @param yaw degrees the player starts turned by: 0 faces the viewer, 180
|
||||
* shows their back
|
||||
*/
|
||||
public SkinPreview(float yaw) {
|
||||
this.yaw = yaw;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
DRAWER.render(renderScope, negativeAngle, angle, scale, ctx);
|
||||
if (DRAWER != null && DRAWER.render(renderScope, width, height, yaw)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String hint = "Join a world to preview";
|
||||
int x = (width - (int) (Fonts.getWidth(hint, Theme.getBodyFont()) * HINT_SCALE)) / 2;
|
||||
renderScope.drawText(HINT_SCALE, hint, x, height / 2, Theme.getBodyFont(), Theme.getMutedFg());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(double mouseX, double mouseY, int button) {
|
||||
dragging = mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
|
||||
if (dragging) {
|
||||
yaw = (yaw - (float) deltaX * DRAG_SPEED) % 360;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(double mouseX, double mouseY, int button) {
|
||||
dragging = false;
|
||||
}
|
||||
|
||||
public static interface EntityDrawer {
|
||||
public void render(RenderScope renderScope, boolean negativeAngle, float angle, float scale,
|
||||
ElementContext ctx);
|
||||
/**
|
||||
* Draws the client's player filling a {@code width} x {@code height} box,
|
||||
* turned by {@code yaw} degrees (0 faces the viewer).
|
||||
*
|
||||
* @return false if there's no player to draw, such as on the title screen
|
||||
*/
|
||||
public boolean render(RenderScope renderScope, int width, int height, float yaw);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.saturnclient.ui.components;
|
||||
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.resources.Fonts;
|
||||
|
||||
/**
|
||||
* A theme preset: a small mock-up of a panel in its colors, and its name.
|
||||
* Clicking it applies the preset.
|
||||
*/
|
||||
public class ThemePresetCard extends Element {
|
||||
public static final int HEIGHT = 92;
|
||||
/** The narrowest card that still fits its preview's widgets */
|
||||
public static final int MIN_WIDTH = 100;
|
||||
|
||||
private static final int PADDING = 10;
|
||||
private static final int PREVIEW_HEIGHT = 44;
|
||||
private static final float NAME_SCALE = 0.68f;
|
||||
|
||||
private final Theme.Preset preset;
|
||||
private final Runnable onApply;
|
||||
|
||||
/**
|
||||
* @param onApply runs after the preset is applied, to refresh what shows the
|
||||
* old colors
|
||||
*/
|
||||
public ThemePresetCard(Theme.Preset preset, int width, Runnable onApply) {
|
||||
this.preset = preset;
|
||||
this.onApply = onApply;
|
||||
this.width = width;
|
||||
this.height = HEIGHT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
boolean active = preset.isActive();
|
||||
int border = active ? Theme.ACCENT.value
|
||||
: Theme.withAlpha(ctx.isHovering() ? 0.22f : 0.06f, Theme.FOREGROUND.value);
|
||||
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, active ? 2 : 1,
|
||||
Theme.PRIMARY.value, border);
|
||||
|
||||
// A panel in the preset's colors: a line of text over an accent and a widget
|
||||
int previewWidth = width - PADDING * 2;
|
||||
renderScope.drawRoundedRectangle(PADDING, PADDING, previewWidth, PREVIEW_HEIGHT, 8, preset.background());
|
||||
renderScope.drawRoundedRectangle(PADDING + 8, PADDING + 8, previewWidth * 6 / 10, 5, 3,
|
||||
Theme.withAlpha(0.8f, preset.foreground()));
|
||||
renderScope.drawRoundedRectangle(PADDING + 8, PADDING + PREVIEW_HEIGHT - 20, 34, 12, 4, preset.accent());
|
||||
renderScope.drawRoundedRectangle(PADDING + 47, PADDING + PREVIEW_HEIGHT - 20, 22, 12, 4, preset.primary());
|
||||
|
||||
int nameY = PADDING + PREVIEW_HEIGHT + (height - PADDING - PREVIEW_HEIGHT - (int) (Fonts.getHeight() * NAME_SCALE)) / 2;
|
||||
renderScope.drawText(NAME_SCALE, preset.name(), PADDING, nameY, Theme.FONT.value, Theme.FOREGROUND.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(int mouseX, int mouseY) {
|
||||
preset.apply();
|
||||
onApply.run();
|
||||
}
|
||||
}
|
||||
@@ -1,206 +1,53 @@
|
||||
package org.saturnclient.ui.components.inputs;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.config.property.ColorProperty;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.Popup;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.elements.Field;
|
||||
|
||||
/**
|
||||
* Shows a color as a swatch and its hex code, and opens a {@link ColorPicker}
|
||||
* beside it when clicked.
|
||||
*/
|
||||
public class ColorInput extends Element {
|
||||
private static final int SWATCH = 20;
|
||||
private static final int PICKER_GAP = 8;
|
||||
|
||||
public ColorProperty prop;
|
||||
|
||||
// Picker state
|
||||
private boolean open = false;
|
||||
private float hue = 0f;
|
||||
private float opacity = 1f;
|
||||
private int selectedX = 0;
|
||||
private int selectedY = 0;
|
||||
|
||||
private boolean draggingOpacity = false;
|
||||
|
||||
// Dimensions
|
||||
private static final int PICKER_WIDTH = 100;
|
||||
private static final int PICKER_HEIGHT = 100;
|
||||
private static final int SLIDER_WIDTH = 100;
|
||||
private static final int SLIDER_HEIGHT = 10;
|
||||
private static final int HUE_SLIDER_HEIGHT = 10;
|
||||
|
||||
// The picker opens to the left, since the input sits at the right of its row.
|
||||
private static final int PICKER_X = -PICKER_WIDTH - 12;
|
||||
private static final int SWATCH = 20;
|
||||
|
||||
// Bounds of the picker's backdrop, relative to the color square
|
||||
private static final int BACKDROP_PADDING = 8;
|
||||
private static final int BACKDROP_TOP = -SLIDER_HEIGHT - 10 - BACKDROP_PADDING;
|
||||
private static final int BACKDROP_BOTTOM = PICKER_HEIGHT + 10 + HUE_SLIDER_HEIGHT + BACKDROP_PADDING;
|
||||
/** Where this was last drawn on the screen, to open the picker beside it */
|
||||
private int originX, originY;
|
||||
|
||||
public ColorInput(ColorProperty prop) {
|
||||
this.prop = prop;
|
||||
this.width = 116;
|
||||
this.height = Field.HEIGHT;
|
||||
syncFromColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(int mouseX, int mouseY) {
|
||||
open = !open;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(double mouseX, double mouseY, int button) {
|
||||
if (!open) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean onInput = mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height;
|
||||
if (!onInput && !isOverPicker(mouseX - PICKER_X, mouseY)) {
|
||||
open = false;
|
||||
return;
|
||||
}
|
||||
|
||||
handlePickerClick(mouseX - PICKER_X, mouseY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
|
||||
if (open) {
|
||||
handlePickerDrag(mouseX - PICKER_X, mouseY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(double mouseX, double mouseY, int button) {
|
||||
draggingOpacity = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
Field.drawBox(renderScope, width, height, ctx.isHovering(), open);
|
||||
originX = ctx.originX;
|
||||
originY = ctx.originY;
|
||||
|
||||
int swatchY = (height - SWATCH) / 2;
|
||||
renderScope.drawRoundedRectangle(swatchY, swatchY, SWATCH, SWATCH, 5, 1, prop.value,
|
||||
Field.drawBox(renderScope, width, height, ctx.isHovering(), Popup.isOpenFor(this));
|
||||
|
||||
int swatchInset = (height - SWATCH) / 2;
|
||||
renderScope.drawRoundedRectangle(swatchInset, swatchInset, SWATCH, SWATCH, 5, 1, prop.value,
|
||||
Theme.withAlpha(0.3f, Theme.FOREGROUND.value));
|
||||
|
||||
renderScope.drawText(Field.TEXT_SCALE, toHex(prop.value), swatchY + SWATCH + 8,
|
||||
renderScope.drawText(Field.TEXT_SCALE, toHex(prop.value), swatchInset + SWATCH + 8,
|
||||
Field.textY(height, Field.TEXT_SCALE), Theme.FONT.value, Theme.FOREGROUND.value);
|
||||
|
||||
if (!open)
|
||||
return;
|
||||
|
||||
int pickerX = PICKER_X;
|
||||
int pickerY = 0;
|
||||
|
||||
// Backdrop behind the picker and its two sliders
|
||||
renderScope.drawRoundedRectangle(pickerX - BACKDROP_PADDING, BACKDROP_TOP,
|
||||
PICKER_WIDTH + BACKDROP_PADDING * 2, BACKDROP_BOTTOM - BACKDROP_TOP,
|
||||
Theme.WIDGET_RADIUS.value, 1, Theme.withAlpha(0.95f, Theme.BACKGROUND.value), Theme.getBorder());
|
||||
|
||||
renderColorSquare(renderScope, pickerX, pickerY);
|
||||
renderOpacitySlider(renderScope, pickerX, pickerY - SLIDER_HEIGHT - 10);
|
||||
renderHueSlider(renderScope, pickerX, pickerY + PICKER_HEIGHT + 10);
|
||||
}
|
||||
|
||||
private void renderColorSquare(RenderScope renderScope, int xStart, int yStart) {
|
||||
for (int x = 0; x < PICKER_WIDTH; x++) {
|
||||
for (int y = 0; y < PICKER_HEIGHT; y++) {
|
||||
float saturation = x / (float) PICKER_WIDTH;
|
||||
float value = 1f - y / (float) PICKER_HEIGHT;
|
||||
int color = Color.HSBtoRGB(hue, saturation, value);
|
||||
int alpha = (int) (opacity * 255) & 0xFF;
|
||||
color = (alpha << 24) | (color & 0xFFFFFF);
|
||||
renderScope.fill(xStart + x, yStart + y, xStart + x + 1, yStart + y + 1, color);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw selection square
|
||||
int selX = xStart + selectedX - 4;
|
||||
int selY = yStart + selectedY - 4;
|
||||
renderScope.fill(selX, selY, selX + 8, selY + 8, 0xFF000000 | prop.value);
|
||||
renderScope.drawBorder(selX, selY, 8, 8, 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
private void renderOpacitySlider(RenderScope renderScope, int xStart, int yStart) {
|
||||
for (int x = 0; x < SLIDER_WIDTH; x++) {
|
||||
float localOpacity = x / (float) SLIDER_WIDTH;
|
||||
int alpha = (int) (localOpacity * 255) & 0xFF;
|
||||
int color = (alpha << 24) | (prop.value & 0xFFFFFF);
|
||||
renderScope.fill(xStart + x, yStart, xStart + x + 1, yStart + SLIDER_HEIGHT, color);
|
||||
}
|
||||
|
||||
int knobX = xStart + (int) (opacity * SLIDER_WIDTH);
|
||||
renderScope.fill(knobX - 1, yStart - 2, knobX + 1, yStart + SLIDER_HEIGHT + 2, 0xFF000000);
|
||||
}
|
||||
|
||||
private void renderHueSlider(RenderScope renderScope, int xStart, int yStart) {
|
||||
for (int x = 0; x < SLIDER_WIDTH; x++) {
|
||||
float localHue = x / (float) SLIDER_WIDTH;
|
||||
int color = Color.HSBtoRGB(localHue, 1f, 1f);
|
||||
renderScope.fill(xStart + x, yStart, xStart + x + 1, yStart + HUE_SLIDER_HEIGHT, 0xFF000000 | color);
|
||||
}
|
||||
|
||||
int knobX = xStart + (int) (hue * SLIDER_WIDTH);
|
||||
renderScope.fill(knobX - 1, yStart - 2, knobX + 1, yStart + HUE_SLIDER_HEIGHT + 2, 0xFF000000);
|
||||
}
|
||||
|
||||
private void handlePickerClick(double mouseX, double mouseY) {
|
||||
// Color square
|
||||
if (mouseX >= 0 && mouseX < PICKER_WIDTH && mouseY >= 0 && mouseY < PICKER_HEIGHT) {
|
||||
selectedX = (int) mouseX;
|
||||
selectedY = (int) mouseY;
|
||||
updateColorFromSelection();
|
||||
}
|
||||
|
||||
// Opacity slider
|
||||
int opY = -SLIDER_HEIGHT - 10;
|
||||
if (mouseX >= 0 && mouseX < SLIDER_WIDTH && mouseY >= opY && mouseY <= opY + SLIDER_HEIGHT) {
|
||||
updateOpacity(mouseX);
|
||||
draggingOpacity = true;
|
||||
}
|
||||
|
||||
// Hue slider
|
||||
int hueY = PICKER_HEIGHT + 10;
|
||||
if (mouseX >= 0 && mouseX < SLIDER_WIDTH && mouseY >= hueY && mouseY <= hueY + HUE_SLIDER_HEIGHT) {
|
||||
updateHue(mouseX);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isOverPicker(double x, double y) {
|
||||
return x >= -BACKDROP_PADDING && x <= PICKER_WIDTH + BACKDROP_PADDING
|
||||
&& y >= BACKDROP_TOP && y <= BACKDROP_BOTTOM;
|
||||
}
|
||||
|
||||
private void handlePickerDrag(double mouseX, double mouseY) {
|
||||
// Opacity
|
||||
if (draggingOpacity)
|
||||
updateOpacity(mouseX);
|
||||
|
||||
// Color square
|
||||
if (mouseX >= 0 && mouseX < PICKER_WIDTH && mouseY >= 0 && mouseY < PICKER_HEIGHT) {
|
||||
selectedX = (int) mouseX;
|
||||
selectedY = (int) mouseY;
|
||||
updateColorFromSelection();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateColorFromSelection() {
|
||||
float saturation = selectedX / (float) PICKER_WIDTH;
|
||||
float value = 1f - selectedY / (float) PICKER_HEIGHT;
|
||||
int color = Color.HSBtoRGB(hue, saturation, value);
|
||||
int alpha = (int) (opacity * 255) & 0xFF;
|
||||
prop.value = (alpha << 24) | (color & 0xFFFFFF);
|
||||
}
|
||||
|
||||
private void updateHue(double mouseX) {
|
||||
hue = (float) Math.max(0, Math.min(1, mouseX / SLIDER_WIDTH));
|
||||
updateColorFromSelection();
|
||||
}
|
||||
|
||||
private void updateOpacity(double mouseX) {
|
||||
opacity = (float) Math.max(0, Math.min(1, mouseX / SLIDER_WIDTH));
|
||||
updateColorFromSelection();
|
||||
@Override
|
||||
public void click(int mouseX, int mouseY) {
|
||||
// The input sits at the right of its row, so the picker opens to its left,
|
||||
// centered on it. While it's open, a click here closes it instead.
|
||||
ColorPicker picker = new ColorPicker(prop);
|
||||
Popup.open(this, picker, originX - PICKER_GAP - picker.width, originY + (height - picker.height) / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -212,16 +59,4 @@ public class ColorInput extends Element {
|
||||
? String.format("#%06X", color & 0xFFFFFF)
|
||||
: String.format("#%08X", color);
|
||||
}
|
||||
|
||||
private void syncFromColor() {
|
||||
int rgb = prop.value & 0xFFFFFF;
|
||||
float[] hsb = Color.RGBtoHSB((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF, null);
|
||||
hue = hsb[0];
|
||||
selectedX = (int) (hsb[1] * PICKER_WIDTH);
|
||||
selectedY = (int) ((1f - hsb[2]) * PICKER_HEIGHT);
|
||||
int alpha = (prop.value >> 24) & 0xFF;
|
||||
if (alpha == 0)
|
||||
alpha = 255;
|
||||
opacity = alpha / 255f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
package org.saturnclient.ui.components.inputs;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import org.saturnclient.common.provider.Providers;
|
||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.config.property.ColorProperty;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
|
||||
/**
|
||||
* A color picker shown as a popup: opacity across the top, a
|
||||
* saturation/brightness square, and hue across the bottom. Each part can be
|
||||
* clicked or dragged.
|
||||
*/
|
||||
public class ColorPicker extends Element {
|
||||
private enum Part {
|
||||
OPACITY, SQUARE, HUE
|
||||
}
|
||||
|
||||
private static final int PADDING = 10;
|
||||
private static final int SIZE = 100;
|
||||
private static final int SLIDER_HEIGHT = 10;
|
||||
private static final int GAP = 10;
|
||||
|
||||
private static final int OPACITY_Y = PADDING;
|
||||
private static final int SQUARE_Y = OPACITY_Y + SLIDER_HEIGHT + GAP;
|
||||
private static final int HUE_Y = SQUARE_Y + SIZE + GAP;
|
||||
|
||||
// Gradient strips, made once and stretched over each part, so a frame is a
|
||||
// few draws instead of one per pixel. Tinting the white ones colors them.
|
||||
private static final int STRIP = 256;
|
||||
/** White fading out to the right: saturation, over the hue */
|
||||
private static final IdentifierRef SATURATION = IdentifierRef.ofSaturn("textures/gui/color_picker/saturation");
|
||||
/** Black fading in downwards: brightness, over the saturation */
|
||||
private static final IdentifierRef BRIGHTNESS = IdentifierRef.ofSaturn("textures/gui/color_picker/brightness");
|
||||
/** White fading in to the right, tinted with the color */
|
||||
private static final IdentifierRef OPACITY = IdentifierRef.ofSaturn("textures/gui/color_picker/opacity");
|
||||
private static final IdentifierRef HUE = IdentifierRef.ofSaturn("textures/gui/color_picker/hue");
|
||||
private static boolean stripsRegistered = false;
|
||||
|
||||
private final ColorProperty prop;
|
||||
|
||||
private float hue;
|
||||
private float saturation;
|
||||
private float brightness;
|
||||
private float opacity;
|
||||
|
||||
/** The part being dragged, or null */
|
||||
private Part dragging = null;
|
||||
|
||||
public ColorPicker(ColorProperty prop) {
|
||||
this.prop = prop;
|
||||
this.width = SIZE + PADDING * 2;
|
||||
this.height = HUE_Y + SLIDER_HEIGHT + PADDING;
|
||||
syncFromColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
registerStrips();
|
||||
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, 1,
|
||||
Theme.getPopupBg(), Theme.withAlpha(0.14f, Theme.FOREGROUND.value));
|
||||
|
||||
renderOpacity(renderScope);
|
||||
renderSquare(renderScope);
|
||||
renderHue(renderScope);
|
||||
}
|
||||
|
||||
private void renderOpacity(RenderScope renderScope) {
|
||||
renderScope.drawTexture(OPACITY, PADDING, OPACITY_Y, 0, 0, SIZE, SLIDER_HEIGHT,
|
||||
0xFF000000 | prop.value);
|
||||
|
||||
drawMarker(renderScope, PADDING + (int) (opacity * SIZE), OPACITY_Y);
|
||||
}
|
||||
|
||||
private void renderSquare(RenderScope renderScope) {
|
||||
renderScope.fill(PADDING, SQUARE_Y, PADDING + SIZE, SQUARE_Y + SIZE, 0xFF000000 | Color.HSBtoRGB(hue, 1f, 1f));
|
||||
renderScope.drawTexture(SATURATION, PADDING, SQUARE_Y, 0, 0, SIZE, SIZE, 0xFFFFFFFF);
|
||||
renderScope.drawTexture(BRIGHTNESS, PADDING, SQUARE_Y, 0, 0, SIZE, SIZE, 0xFFFFFFFF);
|
||||
|
||||
int selX = PADDING + (int) (saturation * SIZE) - 4;
|
||||
int selY = SQUARE_Y + (int) ((1f - brightness) * SIZE) - 4;
|
||||
renderScope.fill(selX, selY, selX + 8, selY + 8, 0xFF000000 | prop.value);
|
||||
renderScope.drawBorder(selX, selY, 8, 8, 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
private void renderHue(RenderScope renderScope) {
|
||||
renderScope.drawTexture(HUE, PADDING, HUE_Y, 0, 0, SIZE, SLIDER_HEIGHT, 0xFFFFFFFF);
|
||||
|
||||
drawMarker(renderScope, PADDING + (int) (hue * SIZE), HUE_Y);
|
||||
}
|
||||
|
||||
private static void registerStrips() {
|
||||
if (stripsRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
BufferedImage saturation = new BufferedImage(STRIP, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
BufferedImage brightness = new BufferedImage(1, STRIP, BufferedImage.TYPE_INT_ARGB);
|
||||
BufferedImage opacity = new BufferedImage(STRIP, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
BufferedImage hue = new BufferedImage(STRIP, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
for (int i = 0; i < STRIP; i++) {
|
||||
int alpha = i * 255 / (STRIP - 1);
|
||||
saturation.setRGB(i, 0, ((255 - alpha) << 24) | 0xFFFFFF);
|
||||
brightness.setRGB(0, i, alpha << 24);
|
||||
opacity.setRGB(i, 0, (alpha << 24) | 0xFFFFFF);
|
||||
hue.setRGB(i, 0, 0xFF000000 | Color.HSBtoRGB(i / (float) STRIP, 1f, 1f));
|
||||
}
|
||||
|
||||
Providers.saturn.registerBufferedImageTexture(SATURATION, saturation);
|
||||
Providers.saturn.registerBufferedImageTexture(BRIGHTNESS, brightness);
|
||||
Providers.saturn.registerBufferedImageTexture(OPACITY, opacity);
|
||||
Providers.saturn.registerBufferedImageTexture(HUE, hue);
|
||||
stripsRegistered = true;
|
||||
}
|
||||
|
||||
private static void drawMarker(RenderScope renderScope, int x, int sliderY) {
|
||||
renderScope.fill(x - 2, sliderY - 2, x + 2, sliderY + SLIDER_HEIGHT + 2, 0xFFFFFFFF);
|
||||
renderScope.fill(x - 1, sliderY - 1, x + 1, sliderY + SLIDER_HEIGHT + 1, 0xFF000000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(double mouseX, double mouseY, int button) {
|
||||
dragging = getPartAt(mouseX, mouseY);
|
||||
update(mouseX, mouseY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
|
||||
update(mouseX, mouseY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(double mouseX, double mouseY, int button) {
|
||||
dragging = null;
|
||||
}
|
||||
|
||||
private Part getPartAt(double mouseX, double mouseY) {
|
||||
if (mouseX < PADDING - 4 || mouseX > PADDING + SIZE + 4) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (mouseY >= OPACITY_Y - 4 && mouseY <= OPACITY_Y + SLIDER_HEIGHT + 4) {
|
||||
return Part.OPACITY;
|
||||
} else if (mouseY >= SQUARE_Y && mouseY <= SQUARE_Y + SIZE) {
|
||||
return Part.SQUARE;
|
||||
} else if (mouseY >= HUE_Y - 4 && mouseY <= HUE_Y + SLIDER_HEIGHT + 4) {
|
||||
return Part.HUE;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the dragged part to the mouse, even when the mouse has left it.
|
||||
*/
|
||||
private void update(double mouseX, double mouseY) {
|
||||
if (dragging == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
float x = clamp01((float) (mouseX - PADDING) / SIZE);
|
||||
|
||||
switch (dragging) {
|
||||
case OPACITY -> opacity = x;
|
||||
case HUE -> hue = x;
|
||||
case SQUARE -> {
|
||||
saturation = x;
|
||||
brightness = 1f - clamp01((float) (mouseY - SQUARE_Y) / SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
int rgb = Color.HSBtoRGB(hue, saturation, brightness) & 0xFFFFFF;
|
||||
prop.value = ((int) (opacity * 255) << 24) | rgb;
|
||||
}
|
||||
|
||||
private void syncFromColor() {
|
||||
int rgb = prop.value & 0xFFFFFF;
|
||||
float[] hsb = Color.RGBtoHSB((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF, null);
|
||||
hue = hsb[0];
|
||||
saturation = hsb[1];
|
||||
brightness = hsb[2];
|
||||
|
||||
int alpha = (prop.value >>> 24) & 0xFF;
|
||||
opacity = (alpha == 0 ? 255 : alpha) / 255f;
|
||||
}
|
||||
|
||||
private static float clamp01(float value) {
|
||||
return Math.max(0f, Math.min(1f, value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.saturnclient.ui.components.inputs;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.ElementRenderer;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
|
||||
/**
|
||||
* A curve dropdown's popup: a {@link CurvePreview} beside the list of curves,
|
||||
* showing the hovered curve, or the current one when none is hovered.
|
||||
*/
|
||||
public class CurveOptions extends Element {
|
||||
public static final int GAP = 8;
|
||||
|
||||
private final List<Element> children;
|
||||
|
||||
public CurveOptions(OptionList list, int current) {
|
||||
CurvePreview preview = new CurvePreview(() -> list.getHovered() >= 0 ? list.getHovered() : current);
|
||||
|
||||
preview.position(0, 0);
|
||||
list.position(preview.width + GAP, 0);
|
||||
this.children = List.of(preview, list);
|
||||
|
||||
this.width = list.x + list.width;
|
||||
this.height = Math.max(preview.height, list.height);
|
||||
}
|
||||
|
||||
/**
|
||||
* How far left of the list this popup starts, so the list can line up
|
||||
* under the dropdown.
|
||||
*/
|
||||
public int getListX() {
|
||||
return children.get(1).x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
ElementRenderer.render(children, ctx.elapsed, renderScope, ctx.mouseX, ctx.mouseY, ctx.childFrame());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(double mouseX, double mouseY, int button) {
|
||||
ElementRenderer.mouseClicked(children, mouseX, mouseY, button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scroll(int mouseX, int mouseY, double horizontalAmount, double verticalAmount) {
|
||||
ElementRenderer.mouseScrolled(children, mouseX, mouseY, horizontalAmount, verticalAmount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package org.saturnclient.ui.components.inputs;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntSupplier;
|
||||
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.anim.Animation;
|
||||
|
||||
/**
|
||||
* A graph of an easing curve, with a dot running along it on a loop to show
|
||||
* how the animation moves.
|
||||
*/
|
||||
public class CurvePreview extends Element {
|
||||
private static final int PADDING = 14;
|
||||
private static final int TITLE_HEIGHT = 20;
|
||||
private static final float TITLE_SCALE = 0.56f;
|
||||
private static final int DOT = 7;
|
||||
|
||||
/** How long the dot takes to run the curve, then how long it rests at the end, in ms */
|
||||
private static final int RUN = 1200;
|
||||
private static final int REST = 500;
|
||||
|
||||
private final IntSupplier curve;
|
||||
|
||||
/**
|
||||
* @param curve the curve to show, as an index {@link Animation#getCurve}
|
||||
* takes; read every frame
|
||||
*/
|
||||
public CurvePreview(IntSupplier curve) {
|
||||
this.curve = curve;
|
||||
this.width = 190;
|
||||
this.height = 150;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, 1,
|
||||
Theme.getPopupBg(), Theme.withAlpha(0.14f, Theme.FOREGROUND.value));
|
||||
|
||||
renderScope.drawText(TITLE_SCALE, "PREVIEW", PADDING, PADDING - 2, Theme.FONT.value, Theme.getMutedFg());
|
||||
|
||||
Function<Double, Double> ease = Animation.getCurve(curve.getAsInt());
|
||||
|
||||
int graphX = PADDING;
|
||||
int graphY = PADDING + TITLE_HEIGHT;
|
||||
int graphWidth = width - PADDING * 2;
|
||||
int graphHeight = height - PADDING * 2 - TITLE_HEIGHT;
|
||||
|
||||
// Curves like "back" overshoot, so fit the graph to the curve's own range.
|
||||
double min = 0, max = 1;
|
||||
for (int i = 0; i <= graphWidth; i++) {
|
||||
double value = ease.apply(i / (double) graphWidth);
|
||||
min = Math.min(min, value);
|
||||
max = Math.max(max, value);
|
||||
}
|
||||
|
||||
double range = max - min;
|
||||
int startY = toY(0, min, range, graphY, graphHeight);
|
||||
int endY = toY(1, min, range, graphY, graphHeight);
|
||||
|
||||
// Where the animation starts and ends
|
||||
int guide = Theme.withAlpha(0.1f, Theme.FOREGROUND.value);
|
||||
renderScope.fill(graphX, startY, graphX + graphWidth, startY + 1, guide);
|
||||
renderScope.fill(graphX, endY, graphX + graphWidth, endY + 1, guide);
|
||||
|
||||
// The curve, as small squares close enough to read as a line
|
||||
int steps = graphWidth * 3;
|
||||
for (int i = 0; i <= steps; i++) {
|
||||
double t = i / (double) steps;
|
||||
int x = graphX + (int) (t * graphWidth);
|
||||
int y = toY(ease.apply(t), min, range, graphY, graphHeight);
|
||||
renderScope.fill(x - 1, y - 1, x + 1, y + 1, Theme.ACCENT.value);
|
||||
}
|
||||
|
||||
// The dot, looping along it
|
||||
double t = Math.min(1.0, (ctx.elapsed % (RUN + REST)) / (double) RUN);
|
||||
int dotX = graphX + (int) (t * graphWidth);
|
||||
int dotY = toY(ease.apply(t), min, range, graphY, graphHeight);
|
||||
renderScope.drawRoundedRectangle(dotX - DOT / 2, dotY - DOT / 2, DOT, DOT, DOT / 2, 2,
|
||||
Theme.FOREGROUND.value, Theme.BACKGROUND.value);
|
||||
}
|
||||
|
||||
private static int toY(double value, double min, double range, int graphY, int graphHeight) {
|
||||
return graphY + graphHeight - (int) ((value - min) / range * graphHeight);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
package org.saturnclient.ui.components.inputs;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
import org.saturnclient.config.ConfigManager;
|
||||
import org.saturnclient.config.property.FloatProperty;
|
||||
import org.saturnclient.ui.elements.Field;
|
||||
@@ -11,7 +14,16 @@ public class FloatInput extends Input {
|
||||
this.width = 96;
|
||||
this.height = Field.HEIGHT;
|
||||
this.prop = prop;
|
||||
this.text = String.valueOf(prop.value);
|
||||
this.text = format(prop.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* The value with at most three decimals and no trailing zeros, since floats
|
||||
* like 1.2000000476837158 can't be stored exactly.
|
||||
*/
|
||||
private static String format(float value) {
|
||||
return new BigDecimal(Float.toString(value)).setScale(3, RoundingMode.HALF_UP).stripTrailingZeros()
|
||||
.toPlainString();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -51,7 +63,7 @@ public class FloatInput extends Input {
|
||||
@Override
|
||||
public void checkReset() {
|
||||
if (prop.isReset) {
|
||||
this.text = String.valueOf(prop.value);
|
||||
this.text = format(prop.value);
|
||||
this.cursorPosition = 0;
|
||||
prop.isReset = false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
package org.saturnclient.ui.components.inputs;
|
||||
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.elements.Field;
|
||||
import org.saturnclient.ui.resources.Fonts;
|
||||
import org.saturnclient.ui.resources.Textures;
|
||||
|
||||
/**
|
||||
* A dropdown's list of options, shown as a popup. Long lists scroll, and the
|
||||
* current option is ticked.
|
||||
*/
|
||||
public class OptionList extends Element {
|
||||
private static final int ITEM_HEIGHT = 30;
|
||||
private static final int PADDING = 4;
|
||||
private static final int MAX_VISIBLE = 8;
|
||||
private static final int CHECK = 14;
|
||||
|
||||
private final String[] options;
|
||||
private final int selected;
|
||||
private final IntConsumer onPick;
|
||||
|
||||
/** Index of the first visible option */
|
||||
private int first;
|
||||
/** Wheel movement not yet turned into whole options, so trackpads scroll smoothly */
|
||||
private double pendingScroll = 0;
|
||||
/** The option under the mouse on the last frame, or -1 */
|
||||
private int hovered = -1;
|
||||
|
||||
public OptionList(String[] options, int selected, int width, IntConsumer onPick) {
|
||||
this.options = options;
|
||||
this.selected = selected;
|
||||
this.onPick = onPick;
|
||||
this.width = width;
|
||||
this.height = Math.min(options.length, MAX_VISIBLE) * ITEM_HEIGHT + PADDING * 2;
|
||||
|
||||
// Start with the current option in view, a few rows down if possible
|
||||
this.first = clampFirst(selected - MAX_VISIBLE / 2 + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, 1,
|
||||
Theme.getPopupBg(), Theme.withAlpha(0.14f, Theme.FOREGROUND.value));
|
||||
|
||||
int visible = Math.min(options.length, MAX_VISIBLE);
|
||||
hovered = getIndexAt(ctx.mouseX, ctx.mouseY);
|
||||
|
||||
for (int row = 0; row < visible; row++) {
|
||||
int index = first + row;
|
||||
int y = PADDING + row * ITEM_HEIGHT;
|
||||
boolean isSelected = index == selected;
|
||||
|
||||
if (index == hovered) {
|
||||
renderScope.drawRoundedRectangle(PADDING, y, width - PADDING * 2, ITEM_HEIGHT,
|
||||
Theme.WIDGET_RADIUS.value - 3, Theme.withAlpha(0.07f, Theme.FOREGROUND.value));
|
||||
}
|
||||
|
||||
int textWidth = width - PADDING * 2 - 12 - CHECK - 10;
|
||||
String text = Fonts.ellipsize(options[index], Theme.FONT.value, Field.TEXT_SCALE, textWidth);
|
||||
renderScope.drawText(Field.TEXT_SCALE, text, PADDING + 10, y + Field.textY(ITEM_HEIGHT, Field.TEXT_SCALE),
|
||||
Theme.FONT.value, isSelected ? Theme.ACCENT.value : Theme.FOREGROUND.value);
|
||||
|
||||
if (isSelected) {
|
||||
renderScope.drawTexture(Textures.ICON_CHECK, width - PADDING - 10 - CHECK,
|
||||
y + (ITEM_HEIGHT - CHECK) / 2, 0, 0, CHECK, CHECK, Theme.ACCENT.value);
|
||||
}
|
||||
}
|
||||
|
||||
// A thin bar showing where in a long list this is
|
||||
if (options.length > MAX_VISIBLE) {
|
||||
int track = height - PADDING * 2;
|
||||
int thumb = Math.max(16, track * MAX_VISIBLE / options.length);
|
||||
int thumbY = PADDING + (track - thumb) * first / (options.length - MAX_VISIBLE);
|
||||
renderScope.drawRoundedRectangle(width - 4, thumbY, 2, thumb, 1,
|
||||
Theme.withAlpha(0.3f, Theme.FOREGROUND.value));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(int mouseX, int mouseY) {
|
||||
int index = getIndexAt(mouseX, mouseY);
|
||||
if (index >= 0) {
|
||||
onPick.accept(index);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scroll(int mouseX, int mouseY, double horizontalAmount, double verticalAmount) {
|
||||
pendingScroll -= verticalAmount;
|
||||
int whole = (int) pendingScroll;
|
||||
pendingScroll -= whole;
|
||||
first = clampFirst(first + whole);
|
||||
}
|
||||
|
||||
/**
|
||||
* The option under the mouse on the last frame, or -1.
|
||||
*/
|
||||
public int getHovered() {
|
||||
return hovered;
|
||||
}
|
||||
|
||||
/**
|
||||
* The option under the mouse, or -1.
|
||||
*/
|
||||
private int getIndexAt(int mouseX, int mouseY) {
|
||||
if (mouseX < PADDING || mouseX > width - PADDING || mouseY < PADDING || mouseY >= height - PADDING) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int index = first + (mouseY - PADDING) / ITEM_HEIGHT;
|
||||
return index < options.length ? index : -1;
|
||||
}
|
||||
|
||||
private int clampFirst(int value) {
|
||||
return Math.max(0, Math.min(value, options.length - Math.min(options.length, MAX_VISIBLE)));
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,30 @@
|
||||
package org.saturnclient.ui.components.inputs;
|
||||
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.config.property.CurveProperty;
|
||||
import org.saturnclient.config.property.SelectProperty;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.Popup;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.elements.Field;
|
||||
import org.saturnclient.ui.resources.Fonts;
|
||||
import org.saturnclient.ui.resources.Textures;
|
||||
|
||||
/**
|
||||
* Steps through a {@link SelectProperty}'s values with arrows on either side.
|
||||
* Clicking the left third goes back; anywhere else goes forward.
|
||||
* A dropdown for a {@link SelectProperty}: the current value, opening a list
|
||||
* of every value when clicked.
|
||||
*/
|
||||
public class Select extends Element {
|
||||
private static final int TEXT_X = 12;
|
||||
private static final int ARROW = 16;
|
||||
private static final int ARROW_INSET = 8;
|
||||
private static final int ARROW_INSET = 10;
|
||||
|
||||
private final SelectProperty prop;
|
||||
|
||||
/** The context this was last drawn with, to open the list next to it */
|
||||
private ElementContext lastContext;
|
||||
|
||||
public Select(SelectProperty prop) {
|
||||
this.prop = prop;
|
||||
this.width = 200;
|
||||
@@ -27,28 +33,34 @@ public class Select extends Element {
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
Field.drawBox(renderScope, width, height, ctx.isHovering(), false);
|
||||
lastContext = ctx;
|
||||
boolean open = Popup.isOpenFor(this);
|
||||
|
||||
boolean prevHovered = ctx.isHovering(0, 0, width / 3, height);
|
||||
boolean nextHovered = ctx.isHovering() && !prevHovered;
|
||||
int arrowY = (height - ARROW) / 2;
|
||||
Field.drawBox(renderScope, width, height, ctx.isHovering(), open);
|
||||
|
||||
renderScope.drawTexture(Textures.ICON_CHEVRON_LEFT, ARROW_INSET, arrowY, 0, 0, ARROW, ARROW,
|
||||
prevHovered ? Theme.ACCENT.value : Theme.getMutedFg());
|
||||
renderScope.drawTexture(Textures.ICON_CHEVRON_RIGHT, width - ARROW_INSET - ARROW, arrowY, 0, 0, ARROW, ARROW,
|
||||
nextHovered ? Theme.ACCENT.value : Theme.getMutedFg());
|
||||
int textWidth = width - TEXT_X - ARROW_INSET - ARROW - 8;
|
||||
String text = Fonts.ellipsize(prop.getSelection(), Theme.FONT.value, Field.TEXT_SCALE, textWidth);
|
||||
renderScope.drawText(Field.TEXT_SCALE, text, TEXT_X, Field.textY(height, Field.TEXT_SCALE), Theme.FONT.value,
|
||||
Theme.FOREGROUND.value);
|
||||
|
||||
int textSpace = width - (ARROW_INSET + ARROW + 4) * 2;
|
||||
String text = Fonts.ellipsize(prop.getSelection(), Theme.FONT.value, Field.TEXT_SCALE, textSpace);
|
||||
Field.drawCenteredText(renderScope, text, width, height, Theme.FONT.value, Theme.FOREGROUND.value);
|
||||
renderScope.drawTexture(Textures.ICON_CHEVRON_DOWN, width - ARROW_INSET - ARROW, (height - ARROW) / 2, 0, 0,
|
||||
ARROW, ARROW, open ? Theme.ACCENT.value : Theme.getMutedFg());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(int mouseX, int mouseY) {
|
||||
if (mouseX < width / 3) {
|
||||
prop.prev();
|
||||
// While the list is open, a click here goes to the popup, which closes it.
|
||||
OptionList list = new OptionList(prop.availableValues, prop.value, width, index -> {
|
||||
prop.setSelection(index);
|
||||
Popup.close();
|
||||
});
|
||||
|
||||
if (prop instanceof CurveProperty) {
|
||||
// The preview goes left of the list, so the list still lines up under this.
|
||||
CurveOptions options = new CurveOptions(list, prop.value);
|
||||
Popup.openBelow(this, options, lastContext, -options.getListX());
|
||||
} else {
|
||||
prop.next();
|
||||
Popup.openBelow(this, list, lastContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
package org.saturnclient.ui.components.inputs;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Locale;
|
||||
import java.util.function.DoubleConsumer;
|
||||
import java.util.function.DoubleSupplier;
|
||||
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.config.property.FloatProperty;
|
||||
import org.saturnclient.config.property.IntProperty;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.elements.Field;
|
||||
|
||||
/**
|
||||
* Edits a number within a range by clicking or dragging along a track, with
|
||||
* the value shown beside it.
|
||||
*/
|
||||
public class Slider extends Element {
|
||||
private static final int TRACK_HEIGHT = 6;
|
||||
private static final int KNOB = 16;
|
||||
private static final int VALUE_WIDTH = 72;
|
||||
private static final int VALUE_GAP = 14;
|
||||
private static final int MAX_DECIMALS = 3;
|
||||
|
||||
private final double min, max, step;
|
||||
private final int decimals;
|
||||
private final String unit;
|
||||
private final DoubleSupplier value;
|
||||
private final DoubleConsumer onChange;
|
||||
|
||||
private boolean dragging = false;
|
||||
|
||||
/**
|
||||
* @param step as text, so a float step like 0.1 isn't read as 0.10000000149
|
||||
*/
|
||||
private Slider(double min, double max, String step, String unit, DoubleSupplier value, DoubleConsumer onChange) {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.step = Double.parseDouble(step);
|
||||
this.unit = unit;
|
||||
this.value = value;
|
||||
this.onChange = onChange;
|
||||
this.decimals = Math.min(MAX_DECIMALS, Math.max(0, new BigDecimal(step).stripTrailingZeros().scale()));
|
||||
this.width = 280;
|
||||
this.height = Field.HEIGHT;
|
||||
}
|
||||
|
||||
public static Slider of(IntProperty prop) {
|
||||
return new Slider(prop.min, prop.max, Integer.toString(prop.step), prop.unit, () -> prop.value,
|
||||
v -> prop.value = (int) Math.round(v));
|
||||
}
|
||||
|
||||
public static Slider of(FloatProperty prop) {
|
||||
return new Slider(prop.min, prop.max, Float.toString(prop.step), prop.unit, () -> prop.value,
|
||||
v -> prop.value = (float) v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
double current = value.getAsDouble();
|
||||
float progress = (float) Math.max(0, Math.min(1, (current - min) / (max - min)));
|
||||
int trackWidth = getTrackWidth();
|
||||
int trackY = (height - TRACK_HEIGHT) / 2;
|
||||
int fillWidth = (int) (trackWidth * progress);
|
||||
|
||||
renderScope.drawRoundedRectangle(KNOB / 2, trackY, trackWidth, TRACK_HEIGHT, TRACK_HEIGHT / 2,
|
||||
Theme.withAlpha(0.14f, Theme.FOREGROUND.value));
|
||||
|
||||
if (fillWidth > 0) {
|
||||
renderScope.drawRoundedRectangle(KNOB / 2, trackY, fillWidth, TRACK_HEIGHT, TRACK_HEIGHT / 2,
|
||||
Theme.ACCENT.value);
|
||||
}
|
||||
|
||||
boolean knobHovered = dragging || ctx.isHovering(fillWidth, 0, KNOB, height);
|
||||
renderScope.drawRoundedRectangle(fillWidth, (height - KNOB) / 2, KNOB, KNOB, KNOB / 2, 3,
|
||||
knobHovered ? Theme.ACCENT.value : Theme.FOREGROUND.value, Theme.BACKGROUND.value);
|
||||
|
||||
// Value
|
||||
int valueX = width - VALUE_WIDTH;
|
||||
renderScope.drawRoundedRectangle(valueX, 0, VALUE_WIDTH, height, Theme.WIDGET_RADIUS.value,
|
||||
Theme.withAlpha(0.07f, Theme.FOREGROUND.value));
|
||||
|
||||
renderScope.getMatrixStack().push();
|
||||
renderScope.getMatrixStack().translate(valueX, 0);
|
||||
Field.drawCenteredText(renderScope, format(current), VALUE_WIDTH, height, Theme.FONT.value,
|
||||
Theme.FOREGROUND.value);
|
||||
renderScope.getMatrixStack().pop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(double mouseX, double mouseY, int button) {
|
||||
dragging = mouseX >= 0 && mouseX <= getTrackWidth() + KNOB && mouseY >= 0 && mouseY <= height;
|
||||
|
||||
if (dragging) {
|
||||
setFromMouse(mouseX);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
|
||||
if (dragging) {
|
||||
setFromMouse(mouseX);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(double mouseX, double mouseY, int button) {
|
||||
dragging = false;
|
||||
}
|
||||
|
||||
private void setFromMouse(double mouseX) {
|
||||
double progress = Math.max(0, Math.min(1, (mouseX - KNOB / 2.0) / getTrackWidth()));
|
||||
double raw = min + progress * (max - min);
|
||||
double snapped = min + Math.round((raw - min) / step) * step;
|
||||
|
||||
// Snapping in floating point can leave 2.9999999, so round to the step's decimals.
|
||||
double rounded = BigDecimal.valueOf(snapped).setScale(decimals, RoundingMode.HALF_UP).doubleValue();
|
||||
onChange.accept(Math.max(min, Math.min(max, rounded)));
|
||||
}
|
||||
|
||||
private String format(double current) {
|
||||
String number = String.format(Locale.ROOT, "%." + decimals + "f", current);
|
||||
|
||||
if (unit.isEmpty()) {
|
||||
return number;
|
||||
}
|
||||
|
||||
// Word units get a space ("700 ms"); symbols don't ("3.0×").
|
||||
return Character.isLetter(unit.charAt(0)) ? number + " " + unit : number + unit;
|
||||
}
|
||||
|
||||
private int getTrackWidth() {
|
||||
return width - VALUE_WIDTH - VALUE_GAP - KNOB;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,8 @@ import org.saturnclient.ui.RenderScope;
|
||||
public class AnimationStagger extends Element {
|
||||
protected List<Element> children = new ArrayList<>();
|
||||
public int delay;
|
||||
/** When the first child starts, in ms */
|
||||
public int start = 0;
|
||||
|
||||
public AnimationStagger(int delay) {
|
||||
this.delay = delay;
|
||||
@@ -22,13 +24,21 @@ public class AnimationStagger extends Element {
|
||||
this.delay = config.stagger.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the children back until {@code start} ms after the screen opens.
|
||||
*/
|
||||
public AnimationStagger after(int start) {
|
||||
this.start = start;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void draw(Element element) {
|
||||
if (!Config.stagger.value) {
|
||||
element.animation = null;
|
||||
}
|
||||
|
||||
if (element.animation != null) {
|
||||
element.animation.delay = delay * children.size();
|
||||
element.animation.delay = start + delay * children.size();
|
||||
}
|
||||
|
||||
ElementRenderer.draw(children, element);
|
||||
@@ -41,7 +51,8 @@ public class AnimationStagger extends Element {
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
ElementRenderer.render(children, ctx.elapsed, renderScope,
|
||||
ctx.mouseX,
|
||||
ctx.mouseY);
|
||||
ctx.mouseY,
|
||||
ctx.childFrame());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,29 +1,47 @@
|
||||
package org.saturnclient.ui.elements;
|
||||
|
||||
import org.saturnclient.config.Config;
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.resources.Fonts;
|
||||
|
||||
/**
|
||||
* A text button: a translucent, outlined surface that brightens on hover.
|
||||
*/
|
||||
public class Button extends Element {
|
||||
private String text;
|
||||
private Runnable onClick;
|
||||
public static final int HEIGHT = 40;
|
||||
|
||||
private static final float TEXT_SCALE = 0.72f;
|
||||
|
||||
private final String text;
|
||||
private final Runnable onClick;
|
||||
|
||||
public Button(String text, Runnable onClick) {
|
||||
this.text = text;
|
||||
this.onClick = onClick;
|
||||
this.dimensions(Fonts.getWidth(text, Theme.FONT.value) + 50, 38);
|
||||
this.width = (int) (Fonts.getWidth(text, Theme.FONT.value) * TEXT_SCALE) + 48;
|
||||
this.height = HEIGHT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value,
|
||||
ctx.isHovering() ? Theme.ACCENT.value : Theme.PRIMARY.value);
|
||||
boolean hovered = ctx.isHovering();
|
||||
drawSurface(renderScope, width, height, hovered);
|
||||
|
||||
renderScope.drawText(text, Fonts.centerX(width, text, Theme.FONT.value), Fonts.centerY(height),
|
||||
Theme.FONT.value,
|
||||
ctx.isHovering() ? Theme.ACCENT_FG.value : Theme.PRIMARY_FG.value);
|
||||
int textX = (width - (int) (Fonts.getWidth(text, Theme.FONT.value) * TEXT_SCALE)) / 2;
|
||||
renderScope.drawText(TEXT_SCALE, text, textX, Field.textY(height, TEXT_SCALE), Theme.FONT.value,
|
||||
hovered ? Theme.FOREGROUND.value : Theme.PRIMARY_FG.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* The button's surface, shared with {@link IconButton}.
|
||||
*/
|
||||
static void drawSurface(RenderScope renderScope, int width, int height, boolean hovered) {
|
||||
renderScope.drawGlassRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, Config.widgetBlur.value, 1,
|
||||
hovered ? Theme.PRIMARY.value : Theme.getPanelBg(),
|
||||
Theme.withAlpha(hovered ? 0.25f : 0.1f, Theme.FOREGROUND.value));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package org.saturnclient.ui.elements;
|
||||
|
||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
|
||||
public class ClickableTexture extends Element {
|
||||
IdentifierRef sprite;
|
||||
private Runnable onClick;
|
||||
|
||||
public ClickableTexture(IdentifierRef sprite, Runnable onClick) {
|
||||
this.sprite = sprite;
|
||||
this.onClick = onClick;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawTexture(sprite, 0, 0, 0, 0, width, height,
|
||||
ctx.isHovering() ? Theme.ACCENT.value : Theme.PRIMARY.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(int mouseX, int mouseY) {
|
||||
if (onClick != null) {
|
||||
onClick.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,8 @@ import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
|
||||
/**
|
||||
* A faint horizontal line across the element's width.
|
||||
* A faint line filling the element: horizontal by default, or vertical when
|
||||
* given a width of 1 and a height.
|
||||
*/
|
||||
public class Divider extends Element {
|
||||
|
||||
@@ -17,6 +18,6 @@ public class Divider extends Element {
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawRectangle(0, 0, width, 1, Theme.getBorder());
|
||||
renderScope.drawRectangle(0, 0, width, height, Theme.getBorder());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.saturnclient.ui.elements;
|
||||
|
||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
|
||||
/**
|
||||
* A square {@link Button} showing an icon, which turns the accent color on
|
||||
* hover.
|
||||
*/
|
||||
public class IconButton extends Element {
|
||||
private static final float ICON_RATIO = 0.48f;
|
||||
|
||||
private final IdentifierRef icon;
|
||||
private final Runnable onClick;
|
||||
|
||||
public IconButton(IdentifierRef icon, Runnable onClick) {
|
||||
this.icon = icon;
|
||||
this.onClick = onClick;
|
||||
this.width = Button.HEIGHT;
|
||||
this.height = Button.HEIGHT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
boolean hovered = ctx.isHovering();
|
||||
Button.drawSurface(renderScope, width, height, hovered);
|
||||
|
||||
int size = (int) (Math.min(width, height) * ICON_RATIO);
|
||||
renderScope.drawTexture(icon, (width - size) / 2, (height - size) / 2, 0, 0, size, size,
|
||||
hovered ? Theme.ACCENT.value : Theme.PRIMARY_FG.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(int mouseX, int mouseY) {
|
||||
onClick.run();
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,9 @@ public class ImageTexture extends Element {
|
||||
if (sprite.toString().endsWith(".svg")) {
|
||||
MinecraftClientRef client = Providers.saturn.getClient();
|
||||
|
||||
int renderWidth = (int) (width * client.getWindow().getFramebufferWidth() / client.getWindow().getWidth());
|
||||
int renderWidth = (int) (width * client.getWindow().getFramebufferWidth() / client.getWindow().getWindowWidth());
|
||||
int renderHeight = (int) (height * client.getWindow().getFramebufferHeight()
|
||||
/ client.getWindow().getHeight());
|
||||
/ client.getWindow().getWindowHeight());
|
||||
|
||||
IdentifierRef pngId = SvgTexture.getSvg(sprite, renderWidth * 2, renderHeight * 2);
|
||||
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
package org.saturnclient.ui.elements;
|
||||
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.config.property.ColorProperty;
|
||||
import org.saturnclient.config.property.Property;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.anim.SlideX;
|
||||
import org.saturnclient.ui.resources.Fonts;
|
||||
import org.saturnclient.ui.resources.Textures;
|
||||
|
||||
public class Notification extends Element {
|
||||
private static ColorProperty error = Property.color(0xFFbf212f);
|
||||
private static ColorProperty info = Property.color(0xFF264b96);
|
||||
private static ColorProperty success = Property.color(0xFF27b376);
|
||||
|
||||
public static enum NotificationKind {
|
||||
Error,
|
||||
Info,
|
||||
Success,
|
||||
}
|
||||
|
||||
NotificationKind kind;
|
||||
String text;
|
||||
String title;
|
||||
|
||||
public Notification(int width, int height, NotificationKind kind, String title, String text) {
|
||||
this.kind = kind;
|
||||
this.text = text;
|
||||
this.title = title;
|
||||
this.width = (int) Math.max(Fonts.getWidth(text, 1) * 0.6f, Fonts.getWidth(title, 1)) + 20;
|
||||
this.height = 50;
|
||||
this.x = width - this.width - 15;
|
||||
this.y = height - this.height - 15;
|
||||
this.animation = new SlideX(700, -width);
|
||||
this.duration = 3000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, 10, Theme.PRIMARY.value);
|
||||
switch (kind) {
|
||||
case Error:
|
||||
renderScope.drawTexture(Textures.CIRCLE_X, 9, 9, 0, 0, 15, 15, error.value);
|
||||
break;
|
||||
|
||||
case Info:
|
||||
renderScope.drawTexture(Textures.INFO, 9, 9, 0, 0, 15, 15, info.value);
|
||||
break;
|
||||
|
||||
case Success:
|
||||
renderScope.drawTexture(Textures.CHECK, 9, 9, 0, 0, 15, 15, success.value);
|
||||
break;
|
||||
}
|
||||
renderScope.drawText(0.7f, title, 32, 10, 1, -1);
|
||||
renderScope.drawText(0.5f, text, 32, 28, 1, -1);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
package org.saturnclient.ui.elements;
|
||||
|
||||
import org.saturnclient.config.Config;
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
|
||||
/**
|
||||
* The translucent, outlined surface that screens lay their content on.
|
||||
* The translucent, outlined surface that screens lay their content on,
|
||||
* frosted over the game where glass is supported.
|
||||
*/
|
||||
public class Panel extends Element {
|
||||
|
||||
@@ -16,7 +18,7 @@ public class Panel extends Element {
|
||||
}
|
||||
|
||||
public static void drawBackground(RenderScope renderScope, int width, int height) {
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.BG_RADIUS.value, 1,
|
||||
renderScope.drawGlassRectangle(0, 0, width, height, Theme.BG_RADIUS.value, Config.menuBlur.value, 1,
|
||||
Theme.getPanelBg(), Theme.getBorder());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,24 +4,40 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.config.property.IntProperty;
|
||||
import org.saturnclient.config.property.Property;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.ElementRenderer;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
|
||||
/**
|
||||
* A vertically scrolling container. It scrolls with the mouse wheel or by
|
||||
* dragging its scrollbar, which shows along its right edge when the children
|
||||
* don't fit.
|
||||
*/
|
||||
public class Scroll extends Element {
|
||||
private static IntProperty scrollBarRadius = Property.integer(10);
|
||||
private static IntProperty scrollBarWidth = Property.integer(5);
|
||||
private static IntProperty scrollBarPadding = Property.integer(5);
|
||||
/** Units scrolled per mouse wheel notch */
|
||||
private static final double SCROLL_SPEED = 24;
|
||||
/** Space left below the last child when scrolled to the end */
|
||||
private static final int END_SPACE = 10;
|
||||
|
||||
private static final int BAR_WIDTH = 5;
|
||||
private static final int BAR_INSET = 5;
|
||||
private static final int MIN_THUMB = 20;
|
||||
|
||||
/** Width to keep clear at the right of the content for the scrollbar and its click area */
|
||||
public static final int SCROLLBAR_SPACE = BAR_WIDTH + BAR_INSET * 2 + 4;
|
||||
|
||||
int padding = 0;
|
||||
boolean background = true;
|
||||
|
||||
protected List<Element> children = new ArrayList<>();
|
||||
int scroll = 0;
|
||||
int maxScroll = 0;
|
||||
|
||||
/** Kept fractional so small trackpad deltas add up instead of being dropped */
|
||||
private double scroll = 0;
|
||||
private int maxScroll = 0;
|
||||
|
||||
/** Where in the thumb it was grabbed, while it's being dragged */
|
||||
private Integer thumbGrab = null;
|
||||
|
||||
public Scroll(int padding) {
|
||||
this.padding = padding;
|
||||
@@ -50,7 +66,8 @@ public class Scroll extends Element {
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
calculateMaxScroll();
|
||||
updateMaxScroll();
|
||||
int offset = getOffset();
|
||||
|
||||
if (background) {
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height,
|
||||
@@ -59,75 +76,28 @@ public class Scroll extends Element {
|
||||
|
||||
renderScope.enableScissor(padding, padding, width - padding, height - padding);
|
||||
renderScope.getMatrixStack().push();
|
||||
renderScope.getMatrixStack().translate(padding, -scroll + padding);
|
||||
renderScope.getMatrixStack().translate(padding, -offset + padding);
|
||||
|
||||
ElementRenderer.render(children, ctx.elapsed, renderScope,
|
||||
ctx.mouseX - padding,
|
||||
ctx.mouseY - padding + scroll);
|
||||
ctx.mouseY - padding + offset,
|
||||
ctx.childFrame(padding, padding - offset, padding, height - padding));
|
||||
|
||||
renderScope.getMatrixStack().pop();
|
||||
renderScope.disableScissor();
|
||||
|
||||
if (maxScroll > 0) {
|
||||
renderScope.drawRoundedRectangle(
|
||||
width - scrollBarWidth.value - scrollBarPadding.value,
|
||||
calculateScrollBarY(),
|
||||
scrollBarWidth.value,
|
||||
calculateScrollBarHeight(),
|
||||
scrollBarRadius.value,
|
||||
Theme.SCROLL.value);
|
||||
boolean active = thumbGrab != null || ctx.isHovering(getBarX() - BAR_INSET, 0, BAR_WIDTH + BAR_INSET * 2,
|
||||
height);
|
||||
|
||||
renderScope.drawRoundedRectangle(getBarX(), getThumbY(), BAR_WIDTH, getThumbHeight(), BAR_WIDTH / 2,
|
||||
active ? Theme.SCROLL.value : Theme.withAlpha(0.7f, Theme.SCROLL.value));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scroll(int mouseX, int mouseY, double horizontalAmount, double verticalAmount) {
|
||||
scroll -= verticalAmount;
|
||||
if (scroll < 0) {
|
||||
scroll = 0;
|
||||
} else if (scroll > maxScroll) {
|
||||
scroll = maxScroll;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Element dimensions(int width, int height) {
|
||||
super.dimensions(width, height);
|
||||
|
||||
maxScroll = 0;
|
||||
for (Element element : children) {
|
||||
maxScroll = Math.max(maxScroll, (element.y + element.height + padding) - (height - padding * 2));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
int calculateScrollBarHeight() {
|
||||
if (maxScroll <= 0)
|
||||
return height - (scrollBarPadding.value * 2);
|
||||
return Math.max(20, (height * height) / (height + maxScroll + 10)) - (scrollBarPadding.value * 2);
|
||||
}
|
||||
|
||||
int calculateScrollBarY() {
|
||||
int scrollBarHeight = calculateScrollBarHeight();
|
||||
if (maxScroll <= 0)
|
||||
return scrollBarPadding.value;
|
||||
int y = (scroll * (height - scrollBarHeight - scrollBarPadding.value * 2)) / maxScroll + scrollBarPadding.value;
|
||||
return Math.min(y, height - scrollBarHeight - scrollBarPadding.value);
|
||||
}
|
||||
|
||||
private void calculateMaxScroll() {
|
||||
maxScroll = 0;
|
||||
|
||||
for (Element element : children) {
|
||||
int bottom = element.y + element.height;
|
||||
maxScroll = Math.max(maxScroll, bottom - height + padding);
|
||||
}
|
||||
|
||||
if (maxScroll > 0) {
|
||||
maxScroll = maxScroll + 10;
|
||||
} else {
|
||||
maxScroll = 0;
|
||||
}
|
||||
setScroll(scroll - verticalAmount * SCROLL_SPEED);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -146,17 +116,80 @@ public class Scroll extends Element {
|
||||
return;
|
||||
}
|
||||
|
||||
ElementRenderer.mouseClicked(children, mouseX - padding, mouseY - padding + scroll, button);
|
||||
if (maxScroll > 0 && mouseX >= getBarX() - BAR_INSET) {
|
||||
int thumbY = getThumbY();
|
||||
int thumbHeight = getThumbHeight();
|
||||
|
||||
// Grab the thumb where it was clicked, or center it on a click in the track.
|
||||
boolean onThumb = mouseY >= thumbY && mouseY <= thumbY + thumbHeight;
|
||||
thumbGrab = onThumb ? (int) mouseY - thumbY : thumbHeight / 2;
|
||||
dragThumb(mouseY);
|
||||
return;
|
||||
}
|
||||
|
||||
ElementRenderer.mouseClicked(children, mouseX - padding, mouseY - padding + getOffset(), button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
|
||||
ElementRenderer.mouseDragged(children, mouseX - padding, mouseY - padding + scroll, button, deltaX,
|
||||
if (thumbGrab != null) {
|
||||
dragThumb(mouseY);
|
||||
return;
|
||||
}
|
||||
|
||||
ElementRenderer.mouseDragged(children, mouseX - padding, mouseY - padding + getOffset(), button, deltaX,
|
||||
deltaY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(double mouseX, double mouseY, int button) {
|
||||
ElementRenderer.mouseReleased(children, mouseX - padding, mouseY - padding + scroll, button);
|
||||
thumbGrab = null;
|
||||
ElementRenderer.mouseReleased(children, mouseX - padding, mouseY - padding + getOffset(), button);
|
||||
}
|
||||
|
||||
private void dragThumb(double mouseY) {
|
||||
int travel = getTrackHeight() - getThumbHeight();
|
||||
if (travel > 0) {
|
||||
setScroll((mouseY - thumbGrab - BAR_INSET) / travel * maxScroll);
|
||||
}
|
||||
}
|
||||
|
||||
private void setScroll(double value) {
|
||||
scroll = Math.max(0, Math.min(maxScroll, value));
|
||||
}
|
||||
|
||||
private int getOffset() {
|
||||
return (int) Math.round(scroll);
|
||||
}
|
||||
|
||||
private void updateMaxScroll() {
|
||||
int bottom = 0;
|
||||
for (Element element : children) {
|
||||
bottom = Math.max(bottom, element.y + element.height);
|
||||
}
|
||||
|
||||
int overflow = bottom + padding * 2 - height;
|
||||
maxScroll = overflow > 0 ? overflow + END_SPACE : 0;
|
||||
|
||||
// The content may have shrunk, such as after filtering.
|
||||
setScroll(scroll);
|
||||
}
|
||||
|
||||
private int getBarX() {
|
||||
return width - BAR_WIDTH - BAR_INSET;
|
||||
}
|
||||
|
||||
private int getTrackHeight() {
|
||||
return height - BAR_INSET * 2;
|
||||
}
|
||||
|
||||
private int getThumbHeight() {
|
||||
int track = getTrackHeight();
|
||||
return Math.max(MIN_THUMB, (int) ((long) track * height / (height + maxScroll)));
|
||||
}
|
||||
|
||||
private int getThumbY() {
|
||||
int travel = getTrackHeight() - getThumbHeight();
|
||||
return BAR_INSET + (int) (travel * (scroll / maxScroll));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
package org.saturnclient.ui.elements;
|
||||
|
||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.Utils;
|
||||
|
||||
public class TabMenu extends Element {
|
||||
public static class TabMenuComponent {
|
||||
Runnable onClick;
|
||||
IdentifierRef sprite;
|
||||
|
||||
public TabMenuComponent(IdentifierRef sprite, Runnable onClick) {
|
||||
this.onClick = onClick;
|
||||
this.sprite = sprite;
|
||||
}
|
||||
}
|
||||
|
||||
TabMenuComponent[] components;
|
||||
public int selectedScreen = 0;
|
||||
|
||||
public TabMenu(int active, TabMenuComponent... components) {
|
||||
this.components = components;
|
||||
width = 25 * components.length + 6;
|
||||
height = 30;
|
||||
this.selectedScreen = active;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value, Theme.PRIMARY.value);
|
||||
|
||||
int col = 0;
|
||||
for (TabMenuComponent component : components) {
|
||||
int x = 25 * col + 5;
|
||||
|
||||
boolean hovering = ctx.isHovering(x, 5, 20, 20) || selectedScreen == col;
|
||||
|
||||
if (hovering) {
|
||||
renderScope.drawRoundedRectangle(x, 5, 20, 20, Theme.WIDGET_RADIUS.value, Theme.ACCENT.value);
|
||||
}
|
||||
|
||||
renderScope.drawTexture(component.sprite, x + 5, 10, 0, 0, 10, 10,
|
||||
hovering ? Theme.ACCENT_FG.value : Theme.PRIMARY_FG.value);
|
||||
|
||||
col++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(int mouseX, int mouseY) {
|
||||
int col = 0;
|
||||
for (TabMenuComponent component : components) {
|
||||
int x = 25 * col + 5;
|
||||
if (Utils.isHovering(mouseX - x, mouseY - 5, 20, 20, 1.0f)) {
|
||||
component.onClick.run();
|
||||
}
|
||||
col++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package org.saturnclient.ui.elements;
|
||||
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.resources.Fonts;
|
||||
|
||||
public class Text extends Element {
|
||||
private String text;
|
||||
|
||||
public Text(String text) {
|
||||
this.text = text;
|
||||
width = Fonts.getWidth(text, Theme.FONT.value);
|
||||
height = Fonts.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawText(this.text, 0, 0, Theme.FONT.value, Theme.FOREGROUND.value);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package org.saturnclient.ui.elements;
|
||||
|
||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
|
||||
public class TextureButton extends Element {
|
||||
IdentifierRef sprite;
|
||||
private Runnable onClick;
|
||||
static int padding = 14;
|
||||
|
||||
public TextureButton(IdentifierRef sprite, Runnable onClick) {
|
||||
this.sprite = sprite;
|
||||
this.onClick = onClick;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value,
|
||||
ctx.isHovering() ? Theme.ACCENT.value : Theme.PRIMARY.value);
|
||||
|
||||
int texWidth = width - padding;
|
||||
int texHeight = height - padding;
|
||||
|
||||
renderScope.drawTexture(sprite, padding / 2, padding / 2, 0, 0, texWidth, texHeight,
|
||||
ctx.isHovering() ? Theme.ACCENT_FG.value : Theme.PRIMARY_FG.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void click(int mouseX, int mouseY) {
|
||||
if (onClick != null) {
|
||||
onClick.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,14 @@
|
||||
package org.saturnclient.ui.resources;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.saturnclient.common.provider.Providers;
|
||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||
|
||||
public class Textures {
|
||||
public static final IdentifierRef CROSSHAIR_RANGE = IdentifierRef
|
||||
.ofSaturn("textures/gui/hud/crosshair_range.png");
|
||||
public static final IdentifierRef COSMETICS = IdentifierRef.ofSaturn("textures/gui/components/cosmetics.png");
|
||||
public static final IdentifierRef HUD_ICON = IdentifierRef.ofSaturn("textures/gui/components/hud.png");
|
||||
public static final IdentifierRef SEARCH = IdentifierRef.ofSaturn("textures/gui/components/search.png");
|
||||
public static final IdentifierRef CLOSE = IdentifierRef.ofSaturn("textures/gui/components/close.png");
|
||||
public static final IdentifierRef MODS_TAB = IdentifierRef.ofSaturn("textures/gui/components/mods_tab.png");
|
||||
public static final IdentifierRef SETTINGS = IdentifierRef.ofSaturn("textures/gui/components/settings.png");
|
||||
public static final IdentifierRef CLOAK = IdentifierRef.ofSaturn("textures/gui/components/cloak.png");
|
||||
public static final IdentifierRef HAT = IdentifierRef.ofSaturn("textures/gui/components/hat.png");
|
||||
public static final IdentifierRef RESET = IdentifierRef.ofSaturn("textures/gui/components/reset.png");
|
||||
public static final IdentifierRef TOGGLE_INDICATOR = IdentifierRef
|
||||
.ofSaturn("textures/gui/components/toggle_indicator.png");
|
||||
public static final IdentifierRef LEFT = IdentifierRef.ofSaturn("textures/gui/components/left.png");
|
||||
public static final IdentifierRef RIGHT = IdentifierRef.ofSaturn("textures/gui/components/right.png");
|
||||
public static final IdentifierRef STORE = IdentifierRef.ofSaturn("textures/gui/components/store.png");
|
||||
public static final IdentifierRef COINS = IdentifierRef.ofSaturn("textures/gui/components/coins.png");
|
||||
public static final IdentifierRef SHIRT = IdentifierRef.ofSaturn("textures/gui/components/shirt.png");
|
||||
public static final IdentifierRef CIRCLE_X = IdentifierRef.ofSaturn("textures/gui/components/circle_x.png");
|
||||
public static final IdentifierRef INFO = IdentifierRef.ofSaturn("textures/gui/components/info.png");
|
||||
public static final IdentifierRef CHECK = IdentifierRef.ofSaturn("textures/gui/components/check.png");
|
||||
|
||||
// Lucide icons (SVG, drawn in white so they can be tinted)
|
||||
public static final IdentifierRef ICON_MODS = IdentifierRef.ofSaturn("textures/gui/components/mods.svg");
|
||||
@@ -33,11 +18,18 @@ public class Textures {
|
||||
public static final IdentifierRef ICON_CLOSE = IdentifierRef.ofSaturn("textures/gui/components/close.svg");
|
||||
public static final IdentifierRef ICON_SEARCH = IdentifierRef.ofSaturn("textures/gui/components/search.svg");
|
||||
public static final IdentifierRef ICON_SLIDERS = IdentifierRef.ofSaturn("textures/gui/components/sliders.svg");
|
||||
public static final IdentifierRef ICON_PALETTE = IdentifierRef.ofSaturn("textures/gui/components/palette.svg");
|
||||
public static final IdentifierRef ICON_ANIMATIONS = IdentifierRef
|
||||
.ofSaturn("textures/gui/components/animations.svg");
|
||||
public static final IdentifierRef ICON_RESET = IdentifierRef.ofSaturn("textures/gui/components/reset.svg");
|
||||
public static final IdentifierRef ICON_CHEVRON_LEFT = IdentifierRef
|
||||
.ofSaturn("textures/gui/components/chevron_left.svg");
|
||||
public static final IdentifierRef ICON_CHEVRON_RIGHT = IdentifierRef
|
||||
.ofSaturn("textures/gui/components/chevron_right.svg");
|
||||
public static final IdentifierRef ICON_CHEVRON_DOWN = IdentifierRef
|
||||
.ofSaturn("textures/gui/components/chevron_down.svg");
|
||||
public static final IdentifierRef ICON_EMOTE = IdentifierRef.ofSaturn("textures/gui/components/emote.svg");
|
||||
public static final IdentifierRef ICON_CHECK = IdentifierRef.ofSaturn("textures/gui/components/check.svg");
|
||||
|
||||
public static final IdentifierRef LOGO = IdentifierRef.ofSaturn("textures/logo/logo.png");
|
||||
public static final IdentifierRef REALISTIC_LOGO = IdentifierRef.ofSaturn("textures/logo/realistic.png");
|
||||
@@ -45,15 +37,17 @@ public class Textures {
|
||||
public static final IdentifierRef LOGO_TEXT_BIG = IdentifierRef.ofSaturn("textures/logo/text_big.png");
|
||||
public static final IdentifierRef SPLASH = IdentifierRef.ofSaturn("textures/gui/splash.png");
|
||||
|
||||
// Widgets
|
||||
public static final IdentifierRef BUTTON_BORDER = IdentifierRef.ofSaturn("button_border");
|
||||
public static final IdentifierRef BUTTON = IdentifierRef.ofSaturn("button");
|
||||
public static final IdentifierRef BUTTON_OP = IdentifierRef.ofSaturn("button_op");
|
||||
public static final IdentifierRef SETTINGS_BG = IdentifierRef.ofSaturn("settings_bg");
|
||||
public static final IdentifierRef RECT = IdentifierRef.ofSaturn("rect");
|
||||
public static final IdentifierRef RECT_BORDER = IdentifierRef.ofSaturn("rect_border");
|
||||
public static final IdentifierRef MOD = IdentifierRef.ofSaturn("textures/gui/components/mod.png");
|
||||
public static final IdentifierRef MOD_BG = IdentifierRef.ofSaturn("textures/gui/components/mod_bg.png");
|
||||
/**
|
||||
* Whether a texture file is in the resources, for falling back when an
|
||||
* optional one (like an emote's preview) is missing.
|
||||
*/
|
||||
public static boolean exists(IdentifierRef texture) {
|
||||
try (InputStream in = Providers.saturn.getClient().getResource(texture)) {
|
||||
return in != null;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static IdentifierRef getCloakPreview(String cloak) {
|
||||
return IdentifierRef.ofSaturn("textures/gui/cloak/" + (cloak.isEmpty() ? "none" : cloak) + ".png");
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
package org.saturnclient.ui.screens;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
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.config.property.ColorProperty;
|
||||
import org.saturnclient.config.property.NamespaceProperty;
|
||||
import org.saturnclient.config.property.Property;
|
||||
import org.saturnclient.ui.PanelScreen;
|
||||
import org.saturnclient.ui.components.ColorGrid;
|
||||
import org.saturnclient.ui.components.PropertyList;
|
||||
import org.saturnclient.ui.components.SectionNav;
|
||||
import org.saturnclient.ui.components.Sidebar;
|
||||
import org.saturnclient.ui.components.ThemePresetCard;
|
||||
import org.saturnclient.ui.elements.ActionButton;
|
||||
import org.saturnclient.ui.elements.Divider;
|
||||
import org.saturnclient.ui.elements.Label;
|
||||
import org.saturnclient.ui.elements.Scroll;
|
||||
import org.saturnclient.ui.resources.Textures;
|
||||
|
||||
/**
|
||||
* The client's own settings, split into sections picked from a list on the left.
|
||||
*/
|
||||
public class ClientSettings extends PanelScreen {
|
||||
private enum Section {
|
||||
GENERAL("General", "Title screen, emotes and cosmetics", Textures.ICON_SLIDERS),
|
||||
UI_STYLE("UI Style", "Colors, corners and font for every Saturn screen", Textures.ICON_PALETTE),
|
||||
ANIMATIONS("Animations", "How long menu animations take, and their easing", Textures.ICON_ANIMATIONS);
|
||||
|
||||
final String title;
|
||||
final String description;
|
||||
final IdentifierRef icon;
|
||||
|
||||
Section(String title, String description, IdentifierRef icon) {
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
this.icon = icon;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int NAV_WIDTH = 170;
|
||||
private static final int NAV_GAP = 16;
|
||||
private static final int LIST_Y = 58;
|
||||
private static final int SECTION_GAP = 22;
|
||||
private static final int PRESET_GAP = 10;
|
||||
|
||||
private Section section = Section.GENERAL;
|
||||
|
||||
public ClientSettings() {
|
||||
super("Settings", Sidebar.SETTINGS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void panel() {
|
||||
List<SectionNav.Item> items = Stream.of(Section.values())
|
||||
.map(s -> new SectionNav.Item(s.icon, s.title))
|
||||
.toList();
|
||||
|
||||
draw(new SectionNav("SATURN CLIENT", items, section.ordinal(), NAV_WIDTH, i -> {
|
||||
section = Section.values()[i];
|
||||
init();
|
||||
}).position(contentX, contentY + 4));
|
||||
|
||||
int dividerX = contentX + NAV_WIDTH + NAV_GAP;
|
||||
draw(new Divider(1).dimensions(1, panelHeight).position(dividerX, panelY));
|
||||
|
||||
int x = dividerX + 1 + NAV_GAP + 4;
|
||||
int width = contentX + contentWidth - x;
|
||||
|
||||
// Header
|
||||
draw(new Label(() -> section.title, 1.15f, () -> Theme.FONT.value, () -> Theme.FOREGROUND.value)
|
||||
.position(x, contentY + 2));
|
||||
draw(new Label(() -> section.description, 0.66f, Theme::getBodyFont, Theme::getMutedFg)
|
||||
.position(x, contentY + 28));
|
||||
|
||||
ActionButton reset = new ActionButton(Textures.ICON_RESET, "Reset", true, this::reset);
|
||||
draw(reset.position(x + width - reset.width, contentY + 4));
|
||||
|
||||
// The section's settings
|
||||
Scroll scroll = new Scroll(0).noBackground();
|
||||
int listWidth = scrollContentWidth(width);
|
||||
|
||||
switch (section) {
|
||||
case GENERAL -> PropertyList.draw(scroll, getGeneralProperties(), Set.of(), listWidth, 0);
|
||||
case UI_STYLE -> drawUiStyle(scroll, listWidth);
|
||||
case ANIMATIONS -> drawAnimations(scroll, listWidth);
|
||||
}
|
||||
|
||||
draw(scroll.dimensions(scrollWidth(width), contentHeight - LIST_Y).position(x, contentY + LIST_Y));
|
||||
}
|
||||
|
||||
private void drawUiStyle(Scroll scroll, int width) {
|
||||
Map<String, Property> properties = Theme.config.getProperties();
|
||||
|
||||
int y = PropertyList.drawSection(scroll, "Presets", 0) + 10;
|
||||
// One row when the cards fit, otherwise as many rows as they need
|
||||
int columns = Math.max(1, Math.min(Theme.PRESETS.size(),
|
||||
(width + PRESET_GAP) / (ThemePresetCard.MIN_WIDTH + PRESET_GAP)));
|
||||
int rows = (Theme.PRESETS.size() + columns - 1) / columns;
|
||||
int cardWidth = (width - PRESET_GAP * (columns - 1)) / columns;
|
||||
|
||||
for (int i = 0; i < Theme.PRESETS.size(); i++) {
|
||||
// Rebuild so the color inputs show the preset's colors
|
||||
scroll.draw(new ThemePresetCard(Theme.PRESETS.get(i), cardWidth, this::init)
|
||||
.position(i % columns * (cardWidth + PRESET_GAP),
|
||||
y + i / columns * (ThemePresetCard.HEIGHT + PRESET_GAP)));
|
||||
}
|
||||
|
||||
y += rows * ThemePresetCard.HEIGHT + (rows - 1) * PRESET_GAP + SECTION_GAP;
|
||||
y = PropertyList.drawSection(scroll, "Colors", y) + 10;
|
||||
y = ColorGrid.draw(scroll, properties, width, y) + SECTION_GAP;
|
||||
|
||||
y = PropertyList.drawSection(scroll, "Shape & type", y) + 4;
|
||||
PropertyList.draw(scroll, properties, getNames(properties, p -> p instanceof ColorProperty), width, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Each menu's animation settings under the menu's name.
|
||||
*/
|
||||
private static void drawAnimations(Scroll scroll, int width) {
|
||||
int y = 0;
|
||||
|
||||
for (AnimationConfig animation : AnimationConfig.ALL) {
|
||||
y = PropertyList.drawSection(scroll, animation.name, y) + 4;
|
||||
y = PropertyList.draw(scroll, animation.getProperties(), Set.of(), width, y) + SECTION_GAP;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The top-level settings. The namespaces have sections of their own.
|
||||
*/
|
||||
private static Map<String, Property> getGeneralProperties() {
|
||||
return Config.config.getProperties().entrySet().stream()
|
||||
.filter(e -> !(e.getValue() instanceof NamespaceProperty))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a,
|
||||
LinkedHashMap::new));
|
||||
}
|
||||
|
||||
private static Set<String> getNames(Map<String, Property> properties, Predicate<Property> filter) {
|
||||
return properties.entrySet().stream()
|
||||
.filter(e -> filter.test(e.getValue()))
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the current section's settings back to their defaults.
|
||||
*/
|
||||
private void reset() {
|
||||
Map<String, Property> properties = switch (section) {
|
||||
case GENERAL -> getGeneralProperties();
|
||||
case UI_STYLE -> Theme.config.getProperties();
|
||||
case ANIMATIONS -> AnimationConfig.config.getProperties();
|
||||
};
|
||||
|
||||
properties.values().forEach(Property::reset);
|
||||
init();
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package org.saturnclient.ui.screens;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.saturnclient.config.ConfigManager;
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui.PanelScreen;
|
||||
import org.saturnclient.ui.components.PropertyList;
|
||||
import org.saturnclient.ui.components.Sidebar;
|
||||
import org.saturnclient.ui.elements.Label;
|
||||
import org.saturnclient.ui.elements.Scroll;
|
||||
|
||||
/**
|
||||
* Edits every property of a config, such as the client's own settings.
|
||||
*/
|
||||
public class ConfigEditor extends PanelScreen {
|
||||
private static final int SCROLLBAR_SPACE = 14;
|
||||
private static final int LIST_Y = 44;
|
||||
|
||||
private final ConfigManager config;
|
||||
|
||||
public ConfigEditor(ConfigManager config) {
|
||||
super("Config Editor", Sidebar.SETTINGS);
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void panel() {
|
||||
draw(new Label(() -> "Settings", 1.15f, () -> Theme.FONT.value, () -> Theme.FOREGROUND.value)
|
||||
.position(contentX, contentY + 2));
|
||||
|
||||
Scroll scroll = new Scroll(0).noBackground();
|
||||
PropertyList.draw(scroll, config.getProperties(), Set.of(), contentWidth, 0);
|
||||
draw(scroll.dimensions(contentWidth + SCROLLBAR_SPACE, contentHeight - LIST_Y)
|
||||
.position(contentX, contentY + LIST_Y));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
package org.saturnclient.ui.screens;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.saturnclient.client.ServiceClient;
|
||||
import org.saturnclient.client.player.SaturnPlayer;
|
||||
import org.saturnclient.common.ref.asset.IdentifierRef;
|
||||
import org.saturnclient.config.AnimationConfig;
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.cosmetics.Cloaks;
|
||||
import org.saturnclient.cosmetics.Hats;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.ElementContext;
|
||||
import org.saturnclient.ui.PanelScreen;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.Utils;
|
||||
import org.saturnclient.ui.anim.SlideFade;
|
||||
import org.saturnclient.ui.components.CosmeticTile;
|
||||
import org.saturnclient.ui.components.Sidebar;
|
||||
import org.saturnclient.ui.components.SkinPreview;
|
||||
import org.saturnclient.ui.elements.AnimationStagger;
|
||||
import org.saturnclient.ui.elements.ChipGroup;
|
||||
import org.saturnclient.ui.elements.Label;
|
||||
import org.saturnclient.ui.elements.Scroll;
|
||||
import org.saturnclient.ui.resources.Fonts;
|
||||
import org.saturnclient.ui.resources.Textures;
|
||||
|
||||
/**
|
||||
* Cloaks and hats: the player on the left, turned by dragging, and a grid of
|
||||
* every cosmetic on the right. Clicking an owned cosmetic equips it; clicking
|
||||
* one not owned yet claims it, which is free.
|
||||
*/
|
||||
public class CosmeticsMenu extends PanelScreen {
|
||||
private enum Kind {
|
||||
CLOAKS("Cloaks", "Cloak", Cloaks.ALL_CLOAKS, () -> Cloaks.availableCloaks, player -> player.cloak,
|
||||
Cloaks::setCloak, ServiceClient::buyCloak, Textures::getCloakPreview, 0.5f, 150f),
|
||||
HATS("Hats", "Hat", Hats.ALL_HATS, () -> Hats.availableHats, player -> player.hat,
|
||||
Hats::setHat, ServiceClient::buyHat, Textures::getHatPreview, 1f, -25f);
|
||||
|
||||
final String title;
|
||||
final String singular;
|
||||
final String[] all;
|
||||
final Supplier<List<String>> owned;
|
||||
final Function<SaturnPlayer, String> equipped;
|
||||
final Consumer<String> equip;
|
||||
final Consumer<String> claim;
|
||||
final Function<String, IdentifierRef> preview;
|
||||
/** Width divided by height of the preview textures */
|
||||
final float previewAspect;
|
||||
/** The angle the player starts at, showing off this kind of cosmetic */
|
||||
final float yaw;
|
||||
|
||||
Kind(String title, String singular, String[] all, Supplier<List<String>> owned,
|
||||
Function<SaturnPlayer, String> equipped, Consumer<String> equip, Consumer<String> claim,
|
||||
Function<String, IdentifierRef> preview, float previewAspect, float yaw) {
|
||||
this.title = title;
|
||||
this.singular = singular;
|
||||
this.all = all;
|
||||
this.owned = owned;
|
||||
this.equipped = equipped;
|
||||
this.equip = equip;
|
||||
this.claim = claim;
|
||||
this.preview = preview;
|
||||
this.previewAspect = previewAspect;
|
||||
this.yaw = yaw;
|
||||
}
|
||||
|
||||
/**
|
||||
* "" (nothing), then every cosmetic of this kind.
|
||||
*/
|
||||
List<String> ids() {
|
||||
List<String> ids = new ArrayList<>();
|
||||
ids.add("");
|
||||
ids.addAll(List.of(all));
|
||||
return ids;
|
||||
}
|
||||
|
||||
String getEquipped() {
|
||||
SaturnPlayer player = SaturnPlayer.get();
|
||||
return player == null ? "" : equipped.apply(player);
|
||||
}
|
||||
}
|
||||
|
||||
/** Names that aren't just the id with each word capitalized */
|
||||
private static final Map<String, String> NAMES = Map.of(
|
||||
"", "None",
|
||||
"amg", "AMG",
|
||||
"amg_petronas", "AMG Petronas",
|
||||
"bmw", "BMW",
|
||||
"redbull", "Red Bull");
|
||||
|
||||
private static final int STAGE_WIDTH = 300;
|
||||
private static final int STAGE_PADDING = 16;
|
||||
private static final int STAGE_FOOTER = 70;
|
||||
private static final int COLUMN_GAP = 20;
|
||||
private static final int COLUMNS = 4;
|
||||
private static final int TILE_GAP = 10;
|
||||
private static final int GRID_Y = 58;
|
||||
private static final float SMALL_SCALE = 0.6f;
|
||||
|
||||
private Kind kind = Kind.CLOAKS;
|
||||
|
||||
public CosmeticsMenu() {
|
||||
super("Cosmetics", Sidebar.COSMETICS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void panel() {
|
||||
drawStage();
|
||||
|
||||
int x = contentX + STAGE_WIDTH + COLUMN_GAP;
|
||||
int width = contentX + contentWidth - x;
|
||||
|
||||
// Header
|
||||
draw(new Label(() -> "Cosmetics", 1.15f, () -> Theme.FONT.value, () -> Theme.FOREGROUND.value)
|
||||
.position(x, contentY + 2));
|
||||
draw(new Label(this::getOwnedSummary, 0.66f, Theme::getBodyFont, Theme::getMutedFg)
|
||||
.position(x, contentY + 28));
|
||||
|
||||
ChipGroup tabs = new ChipGroup(List.of(Kind.CLOAKS.title, Kind.HATS.title), kind.title, selected -> {
|
||||
kind = selected.equals(Kind.HATS.title) ? Kind.HATS : Kind.CLOAKS;
|
||||
init();
|
||||
});
|
||||
draw(tabs.position(x + width - tabs.width, contentY + 4));
|
||||
|
||||
// Grid
|
||||
Scroll scroll = new Scroll(0).noBackground();
|
||||
int gridHeight = contentHeight - GRID_Y;
|
||||
|
||||
if (SaturnPlayer.get() == null) {
|
||||
String message = "Connect to Saturn to change your cosmetics";
|
||||
int messageX = (width - (int) (Fonts.getWidth(message, Theme.getBodyFont()) * 0.7f)) / 2;
|
||||
scroll.draw(new Label(() -> message, 0.7f, Theme::getBodyFont, Theme::getMutedFg)
|
||||
.position(messageX, 60));
|
||||
} else {
|
||||
scroll.draw(createGrid(scrollContentWidth(width)));
|
||||
}
|
||||
|
||||
draw(scroll.dimensions(scrollWidth(width), gridHeight).position(x, contentY + GRID_Y));
|
||||
}
|
||||
|
||||
/**
|
||||
* The left column: the player, and what they have equipped.
|
||||
*/
|
||||
private void drawStage() {
|
||||
draw(new Stage().dimensions(STAGE_WIDTH, contentHeight).position(contentX, contentY));
|
||||
|
||||
int previewY = contentY + STAGE_PADDING + 24;
|
||||
int previewHeight = contentHeight - STAGE_PADDING - 24 - STAGE_FOOTER;
|
||||
draw(new SkinPreview(kind.yaw).dimensions(STAGE_WIDTH, previewHeight).position(contentX, previewY));
|
||||
|
||||
int footerY = contentY + contentHeight - STAGE_FOOTER + 8;
|
||||
draw(new Label(() -> getName(kind.getEquipped()), 1f, () -> Theme.FONT.value, () -> Theme.FOREGROUND.value)
|
||||
.position(contentX + STAGE_PADDING + 4, footerY));
|
||||
draw(new Label(() -> kind.singular + " · Equipped", 0.64f, Theme::getBodyFont, Theme::getMutedFg)
|
||||
.position(contentX + STAGE_PADDING + 4, footerY + 26));
|
||||
}
|
||||
|
||||
private Element createGrid(int width) {
|
||||
int tileWidth = (width - TILE_GAP * (COLUMNS - 1)) / COLUMNS;
|
||||
AnimationStagger grid = new AnimationStagger(AnimationConfig.cosmeticsMenu);
|
||||
List<String> ids = kind.ids();
|
||||
|
||||
for (int i = 0; i < ids.size(); i++) {
|
||||
String id = ids.get(i);
|
||||
Kind k = kind;
|
||||
|
||||
CosmeticTile tile = new CosmeticTile(getName(id), k.preview.apply(id), k.previewAspect, tileWidth,
|
||||
() -> k.owned.get().contains(id),
|
||||
() -> k.getEquipped().equals(id),
|
||||
() -> k.equip.accept(id),
|
||||
() -> k.claim.accept(id));
|
||||
|
||||
tile.position((i % COLUMNS) * (tileWidth + TILE_GAP), (i / COLUMNS) * (CosmeticTile.HEIGHT + TILE_GAP));
|
||||
tile.animation(new SlideFade(AnimationConfig.cosmeticsMenu, -12));
|
||||
grid.draw(tile);
|
||||
}
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
private String getOwnedSummary() {
|
||||
// The owned list also holds "", for nothing equipped.
|
||||
long owned = List.of(kind.all).stream().filter(kind.owned.get()::contains).count();
|
||||
return owned + " of " + kind.all.length + " " + kind.title.toLowerCase(Locale.ROOT)
|
||||
+ " owned · every cosmetic is free to claim";
|
||||
}
|
||||
|
||||
private static String getName(String id) {
|
||||
String name = NAMES.get(id);
|
||||
return name != null ? name : Utils.nameFromId(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* The darker card behind the player, with its heading and a hint that it
|
||||
* can be turned.
|
||||
*/
|
||||
private static class Stage extends Element {
|
||||
@Override
|
||||
public void render(RenderScope renderScope, ElementContext ctx) {
|
||||
renderScope.drawRoundedRectangle(0, 0, width, height, Theme.WIDGET_RADIUS.value + 4, 1,
|
||||
Theme.withAlpha(0.45f, 0xFF000000), Theme.withAlpha(0.05f, Theme.FOREGROUND.value));
|
||||
|
||||
renderScope.drawText(SMALL_SCALE, "PREVIEW", STAGE_PADDING, STAGE_PADDING, Theme.FONT.value,
|
||||
Theme.getMutedFg());
|
||||
|
||||
String hint = "Drag to rotate";
|
||||
int hintWidth = (int) (Fonts.getWidth(hint, Theme.getBodyFont()) * SMALL_SCALE);
|
||||
renderScope.drawText(SMALL_SCALE, hint, width - STAGE_PADDING - hintWidth, STAGE_PADDING,
|
||||
Theme.getBodyFont(), Theme.getMutedFg());
|
||||
|
||||
renderScope.drawRectangle(STAGE_PADDING, height - STAGE_FOOTER, width - STAGE_PADDING * 2, 1,
|
||||
Theme.getBorder());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,110 +1,181 @@
|
||||
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.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.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");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FloatProperty getScaleSetting() {
|
||||
return Config.hudMenuScale;
|
||||
}
|
||||
|
||||
@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.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +1,76 @@
|
||||
package org.saturnclient.ui.screens;
|
||||
|
||||
import org.saturnclient.common.ref.render.MatrixStackRef;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.saturnclient.config.ConfigManager;
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.mod.HudMod;
|
||||
import org.saturnclient.mod.Mod;
|
||||
import org.saturnclient.mod.ModLayout;
|
||||
import org.saturnclient.mod.ModManager;
|
||||
import org.saturnclient.ui.RenderScope;
|
||||
import org.saturnclient.ui.SaturnScreen;
|
||||
import org.saturnclient.ui.components.HudModPreview;
|
||||
import org.saturnclient.ui.resources.Fonts;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Moves and resizes the enabled HUD mods, drawn as they are in game. Drag one
|
||||
* to move it, or drag its corner handle to resize it.
|
||||
*
|
||||
* Unlike other Saturn screens this works in GUI pixels, like the HUD itself,
|
||||
* whatever Saturn's GUI scale.
|
||||
*/
|
||||
public class HudEditor extends SaturnScreen {
|
||||
private static final String HINT = "Drag to move · Drag the corner to resize · Esc when done";
|
||||
|
||||
/** Size of the resize handle, and how far around it still grabs it */
|
||||
private static final int HANDLE = 4;
|
||||
private static final int HANDLE_REACH = 4;
|
||||
private static final float LABEL_SCALE = 0.4f;
|
||||
private static final float HINT_SCALE = 0.45f;
|
||||
|
||||
private final List<HudMod> hudMods = new ArrayList<>();
|
||||
|
||||
private HudMod draggingMod = null;
|
||||
private boolean resizing = false;
|
||||
private int offsetX, offsetY;
|
||||
private static final int RESIZE_MARGIN = 3;
|
||||
|
||||
public HudEditor() {
|
||||
super("HUD Editor");
|
||||
for (Mod m : ModManager.ENABLED_MODS) {
|
||||
if (m.isEnabled() && m instanceof HudMod) {
|
||||
hudMods.add((HudMod) m);
|
||||
if (m.isEnabled() && m instanceof HudMod hudMod) {
|
||||
hudMods.add(hudMod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getScale() {
|
||||
return 1f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ui() {
|
||||
// Placing HUDs needs a clear view of the game.
|
||||
blurBackground = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseClicked(double mouseX, double mouseY, int button) {
|
||||
if (button == 0) {
|
||||
for (int m = hudMods.size() - 1; m >= 0; m--) {
|
||||
HudMod mod = hudMods.get(m);
|
||||
HudMod mod = getModAt(mouseX, mouseY);
|
||||
|
||||
if (mod != null) {
|
||||
ModLayout dim = mod.getDimensions();
|
||||
|
||||
// Check if clicking the corner for resizing
|
||||
if (isCorner(mouseX, mouseY, dim)) {
|
||||
draggingMod = mod;
|
||||
resizing = true;
|
||||
offsetX = (int) (mouseX - (dim.x.value + dim.width * dim.scale.value));
|
||||
offsetY = (int) (mouseY - (dim.y.value + dim.height * dim.scale.value));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if clicking inside the mod
|
||||
if (isInside(mouseX, mouseY, dim)) {
|
||||
draggingMod = mod;
|
||||
resizing = false;
|
||||
offsetX = (int) (mouseX - dim.x.value);
|
||||
offsetY = (int) (mouseY - dim.y.value);
|
||||
return true;
|
||||
}
|
||||
draggingMod = mod;
|
||||
resizing = isOnHandle(mouseX, mouseY, dim);
|
||||
offsetX = (int) (mouseX - (resizing ? right(dim) : dim.x.value));
|
||||
offsetY = (int) (mouseY - (resizing ? bottom(dim) : dim.y.value));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return super.mouseClicked(mouseX, mouseY, button);
|
||||
}
|
||||
|
||||
@@ -72,57 +89,115 @@ public class HudEditor extends SaturnScreen {
|
||||
ModLayout dim = draggingMod.getDimensions();
|
||||
|
||||
if (resizing) {
|
||||
// Scale based on cursor movement
|
||||
float newScale = Math.max(0.5f, ((float) (mouseX - dim.x.value) / dim.width));
|
||||
dim.scale.value = newScale;
|
||||
// The handle follows the cursor: the scale is the new width over the unscaled width.
|
||||
float scale = (float) (mouseX - offsetX - dim.x.value) / dim.width;
|
||||
float min = dim.scale.min != null ? dim.scale.min : 0.5f;
|
||||
float max = dim.scale.max != null ? dim.scale.max : 4f;
|
||||
dim.scale.value = Math.max(min, Math.min(max, scale));
|
||||
} else {
|
||||
// Move the mod
|
||||
dim.x.value = (int) mouseX - offsetX;
|
||||
dim.y.value = (int) mouseY - offsetY;
|
||||
}
|
||||
|
||||
keepOnScreen(dim);
|
||||
}
|
||||
|
||||
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ui() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderScope renderScope, int mouseX, int mouseY, float delta, long elapsed) {
|
||||
HudMod hovered = draggingMod != null ? draggingMod : getModAt(mouseX, mouseY);
|
||||
|
||||
for (HudMod mod : hudMods) {
|
||||
ModLayout dim = mod.getDimensions();
|
||||
MatrixStackRef matrices = renderScope.getMatrixStack();
|
||||
matrices.push();
|
||||
boolean active = mod == hovered;
|
||||
|
||||
matrices.translate(dim.x.value, dim.y.value);
|
||||
matrices.scale(dim.scale.value, dim.scale.value);
|
||||
renderScope.getMatrixStack().push();
|
||||
renderScope.getMatrixStack().translate(dim.x.value, dim.y.value);
|
||||
renderScope.getMatrixStack().scale(dim.scale.value, dim.scale.value);
|
||||
HudModPreview.draw(renderScope, mod);
|
||||
renderScope.getMatrixStack().pop();
|
||||
|
||||
// Render the mod
|
||||
mod.renderDummy(renderScope);
|
||||
drawFrame(renderScope, mod, dim, active);
|
||||
}
|
||||
|
||||
matrices.pop();
|
||||
drawHint(renderScope);
|
||||
}
|
||||
|
||||
// Draw border around mod
|
||||
int borderX = dim.x.value;
|
||||
int borderY = dim.y.value;
|
||||
int borderW = (int) (dim.width * dim.scale.value);
|
||||
int borderH = (int) (dim.height * dim.scale.value);
|
||||
/**
|
||||
* The outline around a HUD, its resize handle, and its name while it's
|
||||
* hovered or dragged.
|
||||
*/
|
||||
private void drawFrame(RenderScope renderScope, HudMod mod, ModLayout dim, boolean active) {
|
||||
int x = dim.x.value;
|
||||
int y = dim.y.value;
|
||||
int w = right(dim) - x;
|
||||
int h = bottom(dim) - y;
|
||||
int color = active ? Theme.ACCENT.value : Theme.withAlpha(0.45f, Theme.FOREGROUND.value);
|
||||
|
||||
renderScope.drawBorder(borderX, borderY, borderW, borderH, 0xFFFFFFFF); // White border
|
||||
if (active) {
|
||||
renderScope.drawRectangle(x, y, w, h, Theme.withAlpha(0.08f, Theme.ACCENT.value));
|
||||
}
|
||||
|
||||
renderScope.drawBorder(x - 1, y - 1, w + 2, h + 2, color);
|
||||
renderScope.drawRectangle(x + w - HANDLE / 2, y + h - HANDLE / 2, HANDLE, HANDLE, color);
|
||||
|
||||
if (active && mod instanceof Mod named) {
|
||||
int labelHeight = (int) (Fonts.getHeight() * LABEL_SCALE);
|
||||
int labelY = y - labelHeight - 4 < 0 ? y + h + 4 : y - labelHeight - 4;
|
||||
renderScope.drawText(LABEL_SCALE, named.getName(), x, labelY, Theme.FONT.value, Theme.FOREGROUND.value);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInside(double mouseX, double mouseY, ModLayout dim) {
|
||||
return mouseX >= dim.x.value && mouseX <= dim.x.value + (dim.width * dim.scale.value) &&
|
||||
mouseY >= dim.y.value && mouseY <= dim.y.value + (dim.height * dim.scale.value);
|
||||
private void drawHint(RenderScope renderScope) {
|
||||
int textWidth = (int) (Fonts.getWidth(HINT, Theme.getBodyFont()) * HINT_SCALE);
|
||||
int textHeight = (int) (Fonts.getHeight() * HINT_SCALE);
|
||||
int pillWidth = textWidth + 20;
|
||||
int pillHeight = textHeight + 10;
|
||||
int x = (width - pillWidth) / 2;
|
||||
int y = 8;
|
||||
|
||||
renderScope.drawRoundedRectangle(x, y, pillWidth, pillHeight, pillHeight / 2, 1, Theme.getPanelBg(),
|
||||
Theme.getBorder());
|
||||
renderScope.drawText(HINT_SCALE, HINT, x + 10, y + 5, Theme.getBodyFont(), Theme.PRIMARY_FG.value);
|
||||
}
|
||||
|
||||
private boolean isCorner(double mouseX, double mouseY, ModLayout dim) {
|
||||
float rightX = dim.x.value + dim.width * dim.scale.value;
|
||||
float bottomY = dim.y.value + dim.height * dim.scale.value;
|
||||
/**
|
||||
* The HUD under the mouse, preferring the one drawn on top, or null.
|
||||
*/
|
||||
private HudMod getModAt(double mouseX, double mouseY) {
|
||||
for (int i = hudMods.size() - 1; i >= 0; i--) {
|
||||
ModLayout dim = hudMods.get(i).getDimensions();
|
||||
if (isOnHandle(mouseX, mouseY, dim) || isInside(mouseX, mouseY, dim)) {
|
||||
return hudMods.get(i);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return Math.abs(mouseX - rightX) < RESIZE_MARGIN && Math.abs(mouseY - bottomY) < RESIZE_MARGIN;
|
||||
private void keepOnScreen(ModLayout dim) {
|
||||
int w = right(dim) - dim.x.value;
|
||||
int h = bottom(dim) - dim.y.value;
|
||||
|
||||
dim.x.value = Math.max(0, Math.min(dim.x.value, width - w));
|
||||
dim.y.value = Math.max(0, Math.min(dim.y.value, height - h));
|
||||
}
|
||||
|
||||
private static int right(ModLayout dim) {
|
||||
return dim.x.value + (int) (dim.width * dim.scale.value);
|
||||
}
|
||||
|
||||
private static int bottom(ModLayout dim) {
|
||||
return dim.y.value + (int) (dim.height * dim.scale.value);
|
||||
}
|
||||
|
||||
private static boolean isInside(double mouseX, double mouseY, ModLayout dim) {
|
||||
return mouseX >= dim.x.value && mouseX <= right(dim) && mouseY >= dim.y.value && mouseY <= bottom(dim);
|
||||
}
|
||||
|
||||
private static boolean isOnHandle(double mouseX, double mouseY, ModLayout dim) {
|
||||
return Math.abs(mouseX - right(dim)) <= HANDLE_REACH && Math.abs(mouseY - bottom(dim)) <= HANDLE_REACH;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -24,7 +24,6 @@ public class ModMenu extends PanelScreen {
|
||||
|
||||
private static final int COLUMNS = 3;
|
||||
private static final int CARD_GAP = 10;
|
||||
private static final int SCROLLBAR_SPACE = 14;
|
||||
|
||||
private String category = ALL;
|
||||
private String query = "";
|
||||
@@ -40,7 +39,7 @@ public class ModMenu extends PanelScreen {
|
||||
protected void panel() {
|
||||
// The search box starts empty after a resize, so the query does too.
|
||||
query = "";
|
||||
gridWidth = contentWidth;
|
||||
gridWidth = scrollContentWidth(contentWidth);
|
||||
|
||||
// Header
|
||||
draw(new Label(() -> "Mods", 1.15f, () -> Theme.FONT.value, () -> Theme.FOREGROUND.value)
|
||||
@@ -66,7 +65,7 @@ public class ModMenu extends PanelScreen {
|
||||
// Mods
|
||||
int scrollY = chipsY + categories.height + 14;
|
||||
scroll = new Scroll(0).noBackground();
|
||||
scroll.dimensions(gridWidth + SCROLLBAR_SPACE, panelY + panelHeight - 12 - scrollY);
|
||||
scroll.dimensions(scrollWidth(contentWidth), panelY + panelHeight - 12 - scrollY);
|
||||
refresh(true);
|
||||
draw(scroll.position(contentX, scrollY));
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ public class ModSettings extends PanelScreen {
|
||||
private static final int PREVIEW_WIDTH = 280;
|
||||
private static final int PREVIEW_MAX_HEIGHT = 220;
|
||||
private static final int COLUMN_GAP = 22;
|
||||
private static final int SCROLLBAR_SPACE = 14;
|
||||
|
||||
private final Mod mod;
|
||||
|
||||
@@ -77,8 +76,9 @@ public class ModSettings extends PanelScreen {
|
||||
}
|
||||
|
||||
Scroll scroll = new Scroll(0).noBackground();
|
||||
PropertyList.draw(scroll, mod.getConfig().getProperties(), Set.of(ENABLED), listWidth, 0);
|
||||
draw(scroll.dimensions(listWidth + SCROLLBAR_SPACE, bodyHeight).position(contentX, bodyY));
|
||||
PropertyList.draw(scroll, mod.getConfig().getProperties(), Set.of(ENABLED), scrollContentWidth(listWidth),
|
||||
0);
|
||||
draw(scroll.dimensions(scrollWidth(listWidth), bodyHeight).position(contentX, bodyY));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,24 +3,35 @@ package org.saturnclient.ui.screens;
|
||||
import org.saturnclient.common.provider.Providers;
|
||||
import org.saturnclient.config.AnimationConfig;
|
||||
import org.saturnclient.config.Config;
|
||||
import org.saturnclient.config.property.FloatProperty;
|
||||
import org.saturnclient.ui.SaturnScreen;
|
||||
import org.saturnclient.ui.anim.Fade;
|
||||
import org.saturnclient.ui.anim.SlideY;
|
||||
import org.saturnclient.ui.elements.AnimationStagger;
|
||||
import org.saturnclient.ui.elements.Button;
|
||||
import org.saturnclient.ui.elements.IconButton;
|
||||
import org.saturnclient.ui.elements.ImageTexture;
|
||||
import org.saturnclient.ui.elements.TextureButton;
|
||||
import org.saturnclient.ui.resources.Textures;
|
||||
import org.saturnclient.ui.screens.cosmetics.CloakMenu;
|
||||
|
||||
/**
|
||||
* The in-game quick menu: the logo over the HUD editor, mod menu and
|
||||
* cosmetics.
|
||||
*/
|
||||
public class ShiftMenu extends SaturnScreen {
|
||||
private static final int MODS_WIDTH = 120;
|
||||
private static final int GAP = 6;
|
||||
|
||||
public ShiftMenu() {
|
||||
super("Shift Menu");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FloatProperty getScaleSetting() {
|
||||
return Config.hudMenuScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ui() {
|
||||
// Logo animations (unchanged)
|
||||
draw(new ImageTexture(Textures.LOGO_TEXT)
|
||||
.dimensions(98, 10)
|
||||
.center(width, height, 0, -36)
|
||||
@@ -31,32 +42,24 @@ public class ShiftMenu extends SaturnScreen {
|
||||
.center(width, height, 0, -80)
|
||||
.animation(new SlideY(AnimationConfig.logo, -20)));
|
||||
|
||||
// Spacing and sizes
|
||||
int btnWidth = 120;
|
||||
int btnHeight = 40;
|
||||
int spacing = 4;
|
||||
|
||||
// Create stagger
|
||||
|
||||
AnimationStagger stagger = new AnimationStagger(AnimationConfig.shiftMenu.stagger.value);
|
||||
int x = 0;
|
||||
|
||||
stagger.draw(new TextureButton(Textures.HUD_ICON, () -> {
|
||||
Providers.saturn.getClient().setScreen(new HudEditor());
|
||||
}).dimensions(btnHeight, btnHeight).position(0, 0)
|
||||
.animation(new Fade(AnimationConfig.shiftMenu)));
|
||||
stagger.draw(new IconButton(Textures.ICON_HUD_EDITOR,
|
||||
() -> Providers.saturn.getClient().setScreen(new HudEditor()))
|
||||
.position(x, 0).animation(new Fade(AnimationConfig.shiftMenu)));
|
||||
x += Button.HEIGHT + GAP;
|
||||
|
||||
stagger.draw(new Button("Settings", () -> {
|
||||
Providers.saturn.getClient().setScreen(new ModMenu());
|
||||
}).dimensions(btnWidth, btnHeight).position(btnHeight + spacing, 0)
|
||||
.animation(new Fade(AnimationConfig.shiftMenu)));
|
||||
stagger.draw(new Button("Mods", () -> Providers.saturn.getClient().setScreen(new ModMenu()))
|
||||
.dimensions(MODS_WIDTH, Button.HEIGHT)
|
||||
.position(x, 0).animation(new Fade(AnimationConfig.shiftMenu)));
|
||||
x += MODS_WIDTH + GAP;
|
||||
|
||||
stagger.draw(new TextureButton(Textures.COSMETICS, () -> {
|
||||
Providers.saturn.getClient().setScreen(new CloakMenu());
|
||||
}).dimensions(btnHeight, btnHeight).position(btnWidth + btnHeight + (spacing * 2), 0)
|
||||
.animation(new Fade(AnimationConfig.shiftMenu)));
|
||||
stagger.draw(new IconButton(Textures.ICON_COSMETICS,
|
||||
() -> Providers.saturn.getClient().setScreen(new CosmeticsMenu()))
|
||||
.position(x, 0).animation(new Fade(AnimationConfig.shiftMenu)));
|
||||
x += Button.HEIGHT;
|
||||
|
||||
// Draw the stagger at the absolute center of the screen
|
||||
draw(stagger.dimensions(btnWidth + (btnHeight * 2) + (spacing * 2), btnHeight).center(width, height, 0,
|
||||
0));
|
||||
draw(stagger.position((width - x) / 2, (height - Button.HEIGHT) / 2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,77 +1,153 @@
|
||||
package org.saturnclient.ui.screens;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.saturnclient.common.provider.Providers;
|
||||
import org.saturnclient.common.ref.game.MinecraftClientRef.MinecraftScreen;
|
||||
import org.saturnclient.config.AnimationConfig;
|
||||
import org.saturnclient.config.Config;
|
||||
import org.saturnclient.config.property.FloatProperty;
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.ui.Element;
|
||||
import org.saturnclient.ui.SaturnScreen;
|
||||
import org.saturnclient.ui.anim.Animation;
|
||||
import org.saturnclient.ui.anim.Fade;
|
||||
import org.saturnclient.ui.anim.SlideFade;
|
||||
import org.saturnclient.ui.anim.SlideY;
|
||||
import org.saturnclient.ui.components.AccountChip;
|
||||
import org.saturnclient.ui.components.Dock;
|
||||
import org.saturnclient.ui.elements.AnimationStagger;
|
||||
import org.saturnclient.ui.elements.Button;
|
||||
import org.saturnclient.ui.elements.IconButton;
|
||||
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;
|
||||
import org.saturnclient.ui.screens.cosmetics.CloakMenu;
|
||||
|
||||
/**
|
||||
* Saturn's title screen: the logo over the game's menus in the middle, the
|
||||
* player and a quit button in the top corners, and along the bottom a dock
|
||||
* (Saturn's settings, its mods and cosmetics, then Minecraft's options) and the version.
|
||||
*/
|
||||
public class TitleMenu extends SaturnScreen {
|
||||
// The logo and wordmark as the shift menu has them: the wordmark over the logo's foot
|
||||
private static final int LOGO = 98;
|
||||
private static final int WORDMARK_WIDTH = 98;
|
||||
private static final int WORDMARK_HEIGHT = 10;
|
||||
private static final int WORDMARK_Y = 88;
|
||||
/** From the logo's foot to the menus, close so they read as one block */
|
||||
private static final int MENUS_GAP = 14;
|
||||
private static final int COLUMN_WIDTH = 280;
|
||||
private static final int BUTTON_HEIGHT = 36;
|
||||
private static final int GAP = 8;
|
||||
/** Space around the account chip, quit button, dock and version, from the screen's edges */
|
||||
private static final int EDGE = 14;
|
||||
/** The top corners sit a little tighter */
|
||||
private static final int TOP_EDGE = 10;
|
||||
/** As tall as the account chip beside it */
|
||||
private static final int QUIT_SIZE = AccountChip.HEIGHT;
|
||||
|
||||
// The intro comes in in reading order, left to right and top to bottom, one step apart
|
||||
private static final int STEP_ACCOUNT = 0;
|
||||
private static final int STEP_QUIT = 1;
|
||||
private static final int STEP_LOGO = 2;
|
||||
private static final int STEP_MENUS = 3;
|
||||
/** After the two menus */
|
||||
private static final int STEP_VERSION = 5;
|
||||
private static final int STEP_DOCK = 6;
|
||||
private static final float VERSION_SCALE = 0.5f;
|
||||
private static final float VERSION_ALPHA = 0.6f;
|
||||
|
||||
public TitleMenu() {
|
||||
super("Title Menu");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FloatProperty getScaleSetting() {
|
||||
return Config.titleScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ui() {
|
||||
backgroundBlur = 0;
|
||||
blurBackground = false;
|
||||
|
||||
draw(new ImageTexture(Textures.LOGO_TEXT).dimensions(98, 10).center(width, height, 0, -40)
|
||||
.animation(new Fade(AnimationConfig.logo.duration.value)));
|
||||
draw(new ImageTexture(Config.getLogo()).dimensions(98, 98).center(width, height, 0, -84)
|
||||
.animation(new SlideY(AnimationConfig.logo, -20)));
|
||||
// Logo and menus, centered together a little above the middle
|
||||
int menusHeight = BUTTON_HEIGHT * 2 + GAP;
|
||||
int blockHeight = LOGO + MENUS_GAP + menusHeight;
|
||||
int top = (height - blockHeight) / 2 - 20;
|
||||
|
||||
AnimationStagger mainButtonStagger = new AnimationStagger(AnimationConfig.mainMenu);
|
||||
draw(new ImageTexture(Config.getLogo())
|
||||
.dimensions(LOGO, LOGO)
|
||||
.position((width - LOGO) / 2, top)
|
||||
.animation(step(new SlideY(AnimationConfig.logo, -20), STEP_LOGO)));
|
||||
draw(new ImageTexture(Textures.LOGO_TEXT)
|
||||
.dimensions(WORDMARK_WIDTH, WORDMARK_HEIGHT)
|
||||
.position((width - WORDMARK_WIDTH) / 2, top + WORDMARK_Y)
|
||||
.animation(step(new Fade(AnimationConfig.logo.duration.value), STEP_LOGO)));
|
||||
|
||||
mainButtonStagger.draw(new Button("SINGLEPLAYER", () -> {
|
||||
Providers.saturn.getClient().setScreen(MinecraftScreen.SelectWorld);
|
||||
}).scale(0.69f).dimensions(420, 52).position(0, 0)
|
||||
.animation(new SlideFade(AnimationConfig.mainMenu, -10)));
|
||||
AnimationStagger menus = new AnimationStagger(AnimationConfig.mainMenu)
|
||||
.after(stepDelay(STEP_MENUS));
|
||||
|
||||
mainButtonStagger.draw(new Button("MULTIPLAYER", () -> {
|
||||
Providers.saturn.getClient().setScreen(MinecraftScreen.Multiplayer);
|
||||
}).scale(0.69f).dimensions(420, 52).position(0, 40)
|
||||
.animation(new SlideFade(AnimationConfig.mainMenu, -10)));
|
||||
addMenuButton(menus, new Button("Singleplayer",
|
||||
() -> Providers.saturn.getClient().setScreen(MinecraftScreen.SelectWorld))
|
||||
.dimensions(COLUMN_WIDTH, BUTTON_HEIGHT).position(0, 0));
|
||||
addMenuButton(menus, new Button("Multiplayer",
|
||||
() -> Providers.saturn.getClient().setScreen(MinecraftScreen.Multiplayer))
|
||||
.dimensions(COLUMN_WIDTH, BUTTON_HEIGHT).position(0, BUTTON_HEIGHT + GAP));
|
||||
|
||||
mainButtonStagger.draw(new Button("OPTIONS", () -> {
|
||||
Providers.saturn.getClient().setScreen(MinecraftScreen.Options);
|
||||
}).scale(0.69f).dimensions(206, 52).position(0, 80)
|
||||
.animation(new SlideFade(AnimationConfig.mainMenu, -10)));
|
||||
draw(menus.position((width - COLUMN_WIDTH) / 2, top + LOGO + MENUS_GAP));
|
||||
|
||||
mainButtonStagger.draw(new Button("QUIT", () -> {
|
||||
Providers.saturn.getClient().scheduleStop();
|
||||
}).scale(0.69f).dimensions(206, 52).position(146, 80)
|
||||
.animation(new SlideFade(AnimationConfig.mainMenu, -10)));
|
||||
// Top corners: the player, and quitting the game
|
||||
draw(new AccountChip()
|
||||
.position(TOP_EDGE, TOP_EDGE)
|
||||
.animation(step(new SlideFade(AnimationConfig.mainMenu, 8), STEP_ACCOUNT)));
|
||||
draw(new IconButton(Textures.ICON_CLOSE, () -> Providers.saturn.getClient().scheduleStop())
|
||||
.dimensions(QUIT_SIZE, QUIT_SIZE)
|
||||
.position(width - TOP_EDGE - QUIT_SIZE, TOP_EDGE)
|
||||
.animation(step(new SlideFade(AnimationConfig.mainMenu, 8), STEP_QUIT)));
|
||||
|
||||
draw(mainButtonStagger.dimensions(289, 143).center(width, height, 0, 50));
|
||||
// Along the bottom: the dock, Saturn first (its settings) and Minecraft's options last, and the version
|
||||
Dock dock = new Dock(List.of(
|
||||
new Dock.Item(Textures.LOGO, () -> Providers.saturn.getClient().setScreen(new ClientSettings())),
|
||||
new Dock.Item(Textures.ICON_MODS, () -> Providers.saturn.getClient().setScreen(new ModMenu())),
|
||||
new Dock.Item(Textures.ICON_COSMETICS,
|
||||
() -> Providers.saturn.getClient().setScreen(new CosmeticsMenu())),
|
||||
new Dock.Item(Textures.ICON_SETTINGS,
|
||||
() -> Providers.saturn.getClient().setScreen(MinecraftScreen.Options))));
|
||||
draw(dock.position((width - dock.width) / 2, height - EDGE - Dock.HEIGHT)
|
||||
.animation(step(new SlideFade(AnimationConfig.mainMenu, -12), STEP_DOCK)));
|
||||
|
||||
int s = Fonts.getHeight() + 20;
|
||||
String version = getVersionText();
|
||||
int versionHeight = (int) (Fonts.getHeight() * VERSION_SCALE);
|
||||
draw(new Label(() -> version, VERSION_SCALE, Theme::getBodyFont,
|
||||
() -> Theme.withAlpha(VERSION_ALPHA, Theme.getMutedFg()))
|
||||
.position(EDGE, height - EDGE - versionHeight)
|
||||
.animation(step(new Fade(AnimationConfig.mainMenu), STEP_VERSION)));
|
||||
}
|
||||
|
||||
AnimationStagger saturnStagger = new AnimationStagger(AnimationConfig.mainMenu.stagger.value);
|
||||
/**
|
||||
* When the intro's {@code step} starts, in ms: the Main Menu stagger apart,
|
||||
* or all at once when staggering is off.
|
||||
*/
|
||||
private static int stepDelay(int step) {
|
||||
return Config.stagger.value ? step * AnimationConfig.mainMenu.stagger.value : 0;
|
||||
}
|
||||
|
||||
saturnStagger.draw(new TextureButton(Textures.HUD_ICON, () -> {
|
||||
Providers.saturn.getClient().setScreen(new HudEditor());
|
||||
}).dimensions(s, s).position(0, 0).animation(new SlideFade(AnimationConfig.mainMenu, s + 8)));
|
||||
private static Animation step(Animation animation, int step) {
|
||||
animation.delay = stepDelay(step);
|
||||
return animation;
|
||||
}
|
||||
|
||||
saturnStagger.draw(new TextureButton(Textures.SETTINGS, () -> {
|
||||
Providers.saturn.getClient().setScreen(new ModMenu());
|
||||
}).dimensions(s, s).position(s + 2, 0)
|
||||
.animation(new SlideFade(AnimationConfig.mainMenu, s + 8)));
|
||||
/**
|
||||
* "Saturn 0.1.0 · 1.21.11", from the mod version "0.1.0+1.21.11".
|
||||
*/
|
||||
private static String getVersionText() {
|
||||
String[] version = Providers.saturn.getModVersion().split("\\+", 2);
|
||||
String text = "Saturn " + version[0];
|
||||
return version.length > 1 ? text + " · " + version[1] : text;
|
||||
}
|
||||
|
||||
saturnStagger.draw(new TextureButton(Textures.CLOAK, () -> {
|
||||
Providers.saturn.getClient().setScreen(new CloakMenu());
|
||||
}).dimensions(s, s).position((s + 2) * 2, 0)
|
||||
.animation(new SlideFade(AnimationConfig.mainMenu, s + 8)));
|
||||
|
||||
draw(saturnStagger.dimensions(((s + 2) * 2) + s, s).centerHorizontal(width, height, 0, 8));
|
||||
private static void addMenuButton(AnimationStagger stagger, Element button) {
|
||||
stagger.draw(button.animation(new SlideFade(AnimationConfig.mainMenu, -10)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
package org.saturnclient.ui.screens.cosmetics;
|
||||
|
||||
import org.saturnclient.client.player.SaturnPlayer;
|
||||
import org.saturnclient.common.provider.Providers;
|
||||
import org.saturnclient.config.AnimationConfig;
|
||||
import org.saturnclient.cosmetics.Cloaks;
|
||||
import org.saturnclient.ui.SaturnScreen;
|
||||
import org.saturnclient.ui.anim.Fade;
|
||||
import org.saturnclient.ui.anim.SlideY;
|
||||
import org.saturnclient.ui.components.CosmeticPreview;
|
||||
import org.saturnclient.ui.components.Sidebar;
|
||||
import org.saturnclient.ui.components.SkinPreview;
|
||||
import org.saturnclient.ui.elements.AnimationStagger;
|
||||
import org.saturnclient.ui.elements.Scroll;
|
||||
import org.saturnclient.ui.elements.TabMenu;
|
||||
import org.saturnclient.ui.elements.TabMenu.TabMenuComponent;
|
||||
import org.saturnclient.ui.resources.Textures;
|
||||
|
||||
public class CloakMenu extends SaturnScreen {
|
||||
public CloakMenu() {
|
||||
super("Cloaks Menu");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ui() {
|
||||
int p = 10;
|
||||
int g = 10;
|
||||
|
||||
Scroll scroll = new Scroll(p);
|
||||
|
||||
int row = 0;
|
||||
int col = 0;
|
||||
|
||||
SaturnPlayer player = SaturnPlayer.get();
|
||||
|
||||
AnimationStagger stagger = new AnimationStagger(AnimationConfig.cosmeticsMenu);
|
||||
|
||||
if (player != null) {
|
||||
for (String cloak : Cloaks.availableCloaks) {
|
||||
|
||||
stagger.draw(
|
||||
new CosmeticPreview(
|
||||
cloak == player.cloak,
|
||||
Textures.getCloakPreview(cloak),
|
||||
() -> Cloaks.setCloak(cloak))
|
||||
.position((50 + g) * col, (111 + g) * row)
|
||||
.animation(new SlideY(AnimationConfig.cosmeticsMenu, 14)));
|
||||
|
||||
if (col == 5) {
|
||||
col = 0;
|
||||
row++;
|
||||
} else {
|
||||
col++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scroll.draw(stagger);
|
||||
|
||||
int scrollWidth = 480 + 10 + (g * 2) + (p * 2);
|
||||
|
||||
draw(scroll
|
||||
.dimensions(scrollWidth, 350)
|
||||
.center(width, height));
|
||||
|
||||
draw(new SkinPreview(-30.0f, false)
|
||||
.scale(3.5f)
|
||||
.position(scroll.x + (scrollWidth - 220), scroll.y + 40)
|
||||
.animation(new Fade(500)));
|
||||
|
||||
draw(new Sidebar(Sidebar.COSMETICS, this.provider::close)
|
||||
.center(width, height, -(scrollWidth / 2 + 35), 0));
|
||||
|
||||
draw(new TabMenu(0,
|
||||
new TabMenuComponent(Textures.CLOAK, () -> {
|
||||
}),
|
||||
new TabMenuComponent(Textures.HAT, () -> {
|
||||
Providers.saturn.getClient().setScreen(new HatMenu());
|
||||
}))
|
||||
.center(width, height, 0, -195));
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package org.saturnclient.ui.screens.cosmetics;
|
||||
|
||||
import org.saturnclient.client.player.SaturnPlayer;
|
||||
import org.saturnclient.common.provider.Providers;
|
||||
import org.saturnclient.config.AnimationConfig;
|
||||
import org.saturnclient.cosmetics.Hats;
|
||||
import org.saturnclient.ui.SaturnScreen;
|
||||
import org.saturnclient.ui.anim.Fade;
|
||||
import org.saturnclient.ui.anim.SlideY;
|
||||
import org.saturnclient.ui.components.CosmeticPreview;
|
||||
import org.saturnclient.ui.components.Sidebar;
|
||||
import org.saturnclient.ui.components.SkinPreview;
|
||||
import org.saturnclient.ui.elements.AnimationStagger;
|
||||
import org.saturnclient.ui.elements.Scroll;
|
||||
import org.saturnclient.ui.elements.TabMenu;
|
||||
import org.saturnclient.ui.elements.TabMenu.TabMenuComponent;
|
||||
import org.saturnclient.ui.resources.Textures;
|
||||
|
||||
public class HatMenu extends SaturnScreen {
|
||||
public HatMenu() {
|
||||
super("Hats Menu");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ui() {
|
||||
int p = 10;
|
||||
int g = 10;
|
||||
|
||||
Scroll scroll = new Scroll(p);
|
||||
|
||||
int row = 0;
|
||||
int col = 0;
|
||||
|
||||
SaturnPlayer player = SaturnPlayer.get();
|
||||
|
||||
AnimationStagger stagger = new AnimationStagger(AnimationConfig.cosmeticsMenu);
|
||||
|
||||
if (player != null) {
|
||||
for (String hat : Hats.availableHats) {
|
||||
stagger.draw(
|
||||
new CosmeticPreview(
|
||||
hat == player.hat,
|
||||
Textures.getHatPreview(hat),
|
||||
() -> Hats.setHat(hat))
|
||||
.dimensions(50, 50)
|
||||
.position((50 + g) * col, (50 + g) * row)
|
||||
.animation(new SlideY(AnimationConfig.cosmeticsMenu, 14)));
|
||||
|
||||
if (col == 5) {
|
||||
col = 0;
|
||||
row++;
|
||||
} else {
|
||||
col++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scroll.draw(stagger);
|
||||
|
||||
int scrollWidth = 480 + 10 + (g * 2) + (p * 2);
|
||||
|
||||
draw(scroll
|
||||
.dimensions(scrollWidth, 350)
|
||||
.center(width, height));
|
||||
|
||||
draw(new SkinPreview(170f, true)
|
||||
.scale(3.5f)
|
||||
.position(scroll.x + (scrollWidth - 220), scroll.y + 40)
|
||||
.animation(new Fade(500)));
|
||||
|
||||
draw(new Sidebar(Sidebar.COSMETICS, this.provider::close)
|
||||
.center(width, height, -(scrollWidth / 2 + 35), 0));
|
||||
|
||||
draw(new TabMenu(1,
|
||||
new TabMenuComponent(Textures.CLOAK, () -> {
|
||||
Providers.saturn.getClient().setScreen(new CloakMenu());
|
||||
}),
|
||||
new TabMenuComponent(Textures.HAT, () -> {
|
||||
}))
|
||||
.center(width, height, 0, -195));
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
package org.saturnclient.ui.screens.store;
|
||||
|
||||
// import org.saturnclient.cosmetics.cloak.Cloaks;
|
||||
import org.saturnclient.client.ServiceClient;
|
||||
import org.saturnclient.client.player.SaturnPlayer;
|
||||
import org.saturnclient.common.provider.Providers;
|
||||
import org.saturnclient.config.AnimationConfig;
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.cosmetics.Cloaks;
|
||||
import org.saturnclient.ui.SaturnScreen;
|
||||
import org.saturnclient.ui.anim.Fade;
|
||||
import org.saturnclient.ui.anim.SlideY;
|
||||
import org.saturnclient.ui.components.CosmeticPreview;
|
||||
import org.saturnclient.ui.components.Sidebar;
|
||||
import org.saturnclient.ui.elements.AnimationStagger;
|
||||
import org.saturnclient.ui.elements.ImageTexture;
|
||||
import org.saturnclient.ui.elements.Scroll;
|
||||
import org.saturnclient.ui.elements.TabMenu;
|
||||
import org.saturnclient.ui.elements.Text;
|
||||
import org.saturnclient.ui.elements.TabMenu.TabMenuComponent;
|
||||
import org.saturnclient.ui.resources.Fonts;
|
||||
import org.saturnclient.ui.resources.Textures;
|
||||
|
||||
// import com.mojang.blaze3d.systems.RenderSystem;
|
||||
|
||||
public class CloakStore extends SaturnScreen {
|
||||
private long lastPurchaseTime = 0;
|
||||
|
||||
public CloakStore() {
|
||||
super("Cloak Store");
|
||||
}
|
||||
|
||||
public CloakStore(long lastPurchaseTime) {
|
||||
super("Cloak Store");
|
||||
this.lastPurchaseTime = lastPurchaseTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ui() {
|
||||
int p = 10;
|
||||
int gx = 10;
|
||||
int gy = 20;
|
||||
|
||||
Scroll scroll = new Scroll(p);
|
||||
|
||||
int row = 0;
|
||||
int col = 0;
|
||||
|
||||
SaturnPlayer player = SaturnPlayer.get();
|
||||
|
||||
AnimationStagger stagger = new AnimationStagger(AnimationConfig.cosmeticsMenu);
|
||||
|
||||
if (player != null) {
|
||||
for (String cloak : Cloaks.ALL_CLOAKS) {
|
||||
if (!Cloaks.availableCloaks.contains(cloak)) {
|
||||
|
||||
int x = (50 + gx) * col;
|
||||
int y = (111 + gy) * row;
|
||||
|
||||
stagger.draw(
|
||||
new CosmeticPreview(
|
||||
cloak == player.cloak,
|
||||
Textures.getCloakPreview(cloak),
|
||||
() -> handlePurchase(cloak))
|
||||
.position(x, y)
|
||||
.animation(new SlideY(AnimationConfig.cosmeticsMenu, 16)));
|
||||
|
||||
String t = "100";
|
||||
|
||||
stagger.draw(
|
||||
new Text(t)
|
||||
.position(x + Fonts.centerX(50, t, Theme.FONT.value), y + 113)
|
||||
.scale(0.5f)
|
||||
.animation(new Fade(400)));
|
||||
|
||||
stagger.draw(
|
||||
new ImageTexture(Textures.COINS)
|
||||
.dimensions(16, 16)
|
||||
.position(x + Fonts.getWidth(t, Theme.FONT.value) - 1, y + 112)
|
||||
.scale(0.5f)
|
||||
.animation(new Fade(400)));
|
||||
|
||||
if (col == 8) {
|
||||
col = 0;
|
||||
row++;
|
||||
} else {
|
||||
col++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scroll.draw(stagger);
|
||||
|
||||
int scrollWidth = 480 + 40 + (gx * 2) + (p * 2);
|
||||
|
||||
draw(scroll
|
||||
.dimensions(scrollWidth, 350)
|
||||
.center(width, height, 15, 0));
|
||||
|
||||
draw(new Sidebar(Sidebar.COSMETICS, this.provider::close)
|
||||
.center(width, height, -((scrollWidth - 30) / 2 + 35), 0));
|
||||
|
||||
draw(new TabMenu(0,
|
||||
new TabMenuComponent(Textures.CLOAK, () -> {
|
||||
}),
|
||||
new TabMenuComponent(Textures.HAT, () -> {
|
||||
Providers.saturn.getClient().setScreen(new HatStore());
|
||||
}))
|
||||
.center(width, height, 0, -195));
|
||||
}
|
||||
|
||||
private void handlePurchase(String cloak) {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
if (now - lastPurchaseTime >= 3000) {
|
||||
// Utils.notify(NotificationKind.Info, "Purchase processing", "Please wait");
|
||||
|
||||
ServiceClient.buyCloak(cloak);
|
||||
lastPurchaseTime = now;
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
|
||||
// RenderSystem.recordRenderCall(() -> {
|
||||
// MinecraftProvider.PROVIDER.setScreen(new CloakStore(now));
|
||||
// // Utils.notify(NotificationKind.Success,
|
||||
// // "Purchase complete",
|
||||
// // "Congrats, enjoy your new cloak!");
|
||||
// });
|
||||
}).start();
|
||||
|
||||
} else {
|
||||
// Utils.notify(NotificationKind.Error,
|
||||
// "Timeout error",
|
||||
// "Please wait 3 seconds");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
package org.saturnclient.ui.screens.store;
|
||||
|
||||
import org.saturnclient.client.ServiceClient;
|
||||
import org.saturnclient.client.player.SaturnPlayer;
|
||||
import org.saturnclient.common.provider.Providers;
|
||||
import org.saturnclient.config.AnimationConfig;
|
||||
import org.saturnclient.config.Theme;
|
||||
import org.saturnclient.cosmetics.Hats;
|
||||
import org.saturnclient.ui.SaturnScreen;
|
||||
import org.saturnclient.ui.anim.Fade;
|
||||
import org.saturnclient.ui.anim.SlideY;
|
||||
import org.saturnclient.ui.components.CosmeticPreview;
|
||||
import org.saturnclient.ui.components.Sidebar;
|
||||
import org.saturnclient.ui.elements.AnimationStagger;
|
||||
import org.saturnclient.ui.elements.ImageTexture;
|
||||
import org.saturnclient.ui.elements.Scroll;
|
||||
import org.saturnclient.ui.elements.TabMenu;
|
||||
import org.saturnclient.ui.elements.Text;
|
||||
import org.saturnclient.ui.elements.TabMenu.TabMenuComponent;
|
||||
import org.saturnclient.ui.resources.Fonts;
|
||||
import org.saturnclient.ui.resources.Textures;
|
||||
|
||||
// import com.mojang.blaze3d.systems.RenderSystem;
|
||||
|
||||
public class HatStore extends SaturnScreen {
|
||||
private long lastPurchaseTime = 0;
|
||||
|
||||
public HatStore() {
|
||||
super("Hat Store");
|
||||
}
|
||||
|
||||
public HatStore(long lastPurchaseTime) {
|
||||
super("Hat Store");
|
||||
this.lastPurchaseTime = lastPurchaseTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ui() {
|
||||
int p = 10;
|
||||
int gx = 10;
|
||||
int gy = 20;
|
||||
|
||||
Scroll scroll = new Scroll(p);
|
||||
|
||||
int row = 0;
|
||||
int col = 0;
|
||||
|
||||
SaturnPlayer player = SaturnPlayer.get();
|
||||
|
||||
AnimationStagger stagger = new AnimationStagger(AnimationConfig.cosmeticsMenu);
|
||||
|
||||
if (player != null) {
|
||||
for (String hat : Hats.ALL_HATS) {
|
||||
if (!Hats.availableHats.contains(hat)) {
|
||||
|
||||
int x = (50 + gx) * col;
|
||||
int y = (50 + gy) * row;
|
||||
|
||||
stagger.draw(
|
||||
new CosmeticPreview(
|
||||
hat == player.hat,
|
||||
Textures.getHatPreview(hat),
|
||||
() -> handlePurchase(hat))
|
||||
.dimensions(50, 50)
|
||||
.position(x, y)
|
||||
.animation(new SlideY(AnimationConfig.cosmeticsMenu, 16)));
|
||||
|
||||
String t = "50";
|
||||
|
||||
stagger.draw(
|
||||
new Text(t)
|
||||
.position(x + Fonts.centerX(50, t, Theme.FONT.value), y + 53)
|
||||
.scale(0.5f)
|
||||
.animation(new Fade(400)));
|
||||
|
||||
stagger.draw(
|
||||
new ImageTexture(Textures.COINS)
|
||||
.dimensions(16, 16)
|
||||
.position(x + Fonts.getWidth(t, Theme.FONT.value) + 4, y + 52)
|
||||
.scale(0.5f)
|
||||
.animation(new Fade(400)));
|
||||
|
||||
if (col == 8) {
|
||||
col = 0;
|
||||
row++;
|
||||
} else {
|
||||
col++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scroll.draw(stagger);
|
||||
|
||||
int scrollWidth = 480 + 40 + (gx * 2) + (p * 2);
|
||||
|
||||
draw(scroll
|
||||
.dimensions(scrollWidth, 350)
|
||||
.center(width, height, 15, 0));
|
||||
|
||||
draw(new Sidebar(Sidebar.COSMETICS, this.provider::close)
|
||||
.center(width, height, -((scrollWidth - 30) / 2 + 35), 0));
|
||||
|
||||
draw(new TabMenu(1,
|
||||
new TabMenuComponent(Textures.CLOAK, () -> {
|
||||
Providers.saturn.getClient().setScreen(new CloakStore());
|
||||
}),
|
||||
new TabMenuComponent(Textures.HAT, () -> {
|
||||
}))
|
||||
.center(width, height, 0, -195));
|
||||
}
|
||||
|
||||
private void handlePurchase(String hat) {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
if (now - lastPurchaseTime >= 3000) {
|
||||
// Utils.notify(NotificationKind.Info, "Purchase processing", "Please wait");
|
||||
|
||||
ServiceClient.buyHat(hat);
|
||||
lastPurchaseTime = now;
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
|
||||
// RenderSystem.recordRenderCall(() -> {
|
||||
// MinecraftProvider.PROVIDER.setScreen(new HatStore(now));
|
||||
// // Utils.notify(NotificationKind.Success,
|
||||
// // "Purchase complete",
|
||||
// // "Congrats, enjoy your new hat!");
|
||||
// });
|
||||
}).start();
|
||||
|
||||
} else {
|
||||
// Utils.notify(NotificationKind.Error,
|
||||
// "Timeout error",
|
||||
// "Please wait 3 seconds");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
{
|
||||
"version": 3,
|
||||
"name": "\u0414\u044d\u0431",
|
||||
"description": "\u0414\u044d\u0431\u043d\u0438 \u0441\u043e \u0432\u0441\u0435\u043c \u0432\u043a\u0443\u0441\u043e\u043c",
|
||||
"author": "SPEmotes",
|
||||
"emote": {
|
||||
"beginTick": 0,
|
||||
"endTick": 30,
|
||||
"stopTick": 31,
|
||||
"isLoop": false,
|
||||
"returnTick": 1,
|
||||
"easeBeforeKeyframe": false,
|
||||
"degrees": false,
|
||||
"moves": [
|
||||
{
|
||||
"tick": 4,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"rightLeg": {
|
||||
"x": -1.873698,
|
||||
"y": 11.100854,
|
||||
"z": 0.104908,
|
||||
"pitch": -0.01179,
|
||||
"yaw": 0.000183,
|
||||
"roll": 0.037555
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 7,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"rightArm": {
|
||||
"x": -5.06384,
|
||||
"y": 3.129459,
|
||||
"z": -0.028821,
|
||||
"pitch": 1.006445,
|
||||
"yaw": 0.326442,
|
||||
"roll": 1.238234
|
||||
},
|
||||
"leftArm": {
|
||||
"x": 5.0,
|
||||
"y": 2.0,
|
||||
"z": 0.0,
|
||||
"pitch": -1.859405,
|
||||
"yaw": 0.56235,
|
||||
"roll": -1.6982
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 8,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"head": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.14541,
|
||||
"yaw": 0.336522,
|
||||
"roll": -0.066829
|
||||
},
|
||||
"body": {
|
||||
"x": 0.0,
|
||||
"y": 0.010776,
|
||||
"z": 0.0,
|
||||
"pitch": -0.007742,
|
||||
"yaw": -0.304674,
|
||||
"roll": -0.059173
|
||||
},
|
||||
"rightArm": {
|
||||
"bend": -1.042365
|
||||
},
|
||||
"leftArm": {
|
||||
"bend": -1.205889
|
||||
},
|
||||
"rightLeg": {
|
||||
"x": -1.9,
|
||||
"y": 12.0,
|
||||
"z": 0.1,
|
||||
"pitch": -0.023581,
|
||||
"yaw": 0.001468,
|
||||
"roll": 0.075111
|
||||
},
|
||||
"leftLeg": {
|
||||
"x": 1.9,
|
||||
"y": 12.0,
|
||||
"z": -0.1,
|
||||
"pitch": 0.025482,
|
||||
"yaw": -0.467862,
|
||||
"roll": 0.006087
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 12,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"leftLeg": {
|
||||
"x": 1.933101,
|
||||
"y": 11.441392,
|
||||
"z": -0.084124,
|
||||
"pitch": 0.021479,
|
||||
"yaw": -0.538523,
|
||||
"roll": -0.038416
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 16,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"body": {
|
||||
"x": 0.0,
|
||||
"y": 0.010776,
|
||||
"z": 0.0,
|
||||
"pitch": -0.043213,
|
||||
"yaw": 0.303575,
|
||||
"roll": -0.059152
|
||||
},
|
||||
"rightArm": {
|
||||
"bend": -1.396377
|
||||
},
|
||||
"leftArm": {
|
||||
"bend": 0.0
|
||||
},
|
||||
"rightLeg": {
|
||||
"x": -1.9,
|
||||
"y": 12.0,
|
||||
"z": 0.1,
|
||||
"pitch": -0.018974,
|
||||
"yaw": 0.303724,
|
||||
"roll": 0.068059
|
||||
},
|
||||
"leftLeg": {
|
||||
"x": 1.9,
|
||||
"y": 12.0,
|
||||
"z": -0.1,
|
||||
"pitch": 0.017477,
|
||||
"yaw": -0.548618,
|
||||
"roll": -0.082919
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 18,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"head": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.291391,
|
||||
"yaw": 0.542094,
|
||||
"roll": -0.159259
|
||||
},
|
||||
"rightArm": {
|
||||
"x": -5.06384,
|
||||
"y": 3.129459,
|
||||
"z": -0.028821,
|
||||
"pitch": -1.265057,
|
||||
"yaw": 0.037433,
|
||||
"roll": 1.364253
|
||||
},
|
||||
"leftArm": {
|
||||
"x": 5.0,
|
||||
"y": 2.0,
|
||||
"z": 0.0,
|
||||
"pitch": -0.438223,
|
||||
"yaw": -0.045192,
|
||||
"roll": -1.798213
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 21,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"head": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.291391,
|
||||
"yaw": 0.542094,
|
||||
"roll": -0.159259
|
||||
},
|
||||
"rightArm": {
|
||||
"x": -5.06384,
|
||||
"y": 3.129459,
|
||||
"z": -0.028821,
|
||||
"pitch": -1.265057,
|
||||
"yaw": 0.037433,
|
||||
"roll": 1.364253
|
||||
},
|
||||
"leftArm": {
|
||||
"x": 5.0,
|
||||
"y": 2.0,
|
||||
"z": 0.0,
|
||||
"pitch": -0.438223,
|
||||
"yaw": -0.045192,
|
||||
"roll": -1.798213
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,697 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"valid": true,
|
||||
"name": "front_flip",
|
||||
"description": "Parkour!!!!1",
|
||||
"author": "DKinchik",
|
||||
"emote": {
|
||||
"beginTick": 0,
|
||||
"endTick": 17,
|
||||
"stopTick": 18,
|
||||
"isLoop": false,
|
||||
"returnTick": 1,
|
||||
"nsfw": false,
|
||||
"degrees": false,
|
||||
"moves": [
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"x": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"x": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"y": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"y": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.7020477
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.7020477
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"head": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"x": -1.9
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"x": -1.9
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"y": 11.169379
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 3,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"y": 12.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"y": 12.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"z": -0.39214998
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 3,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"z": 0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"z": 0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 3,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"pitch": -1.9707682
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"pitch": -1.9707682
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"roll": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"roll": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightLeg": {
|
||||
"bend": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"x": -5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"x": -5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -1.3665518
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -1.3665518
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"roll": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"rightArm": {
|
||||
"bend": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"x": 5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"x": 5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"y": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"pitch": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"pitch": -1.0793308
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"pitch": -1.0793308
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"pitch": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftArm": {
|
||||
"bend": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"x": 1.9
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"x": 1.9
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"y": 10.861001
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 3,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"y": 12.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"y": 12.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"z": -0.5082285
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 3,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"z": -0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"z": -0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 3,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"pitch": -1.9850811
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"pitch": -1.9850811
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"pitch": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"yaw": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"roll": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"leftLeg": {
|
||||
"bend": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"x": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 16,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"x": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"y": -0.061333913
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 7,
|
||||
"easing": "OUTQUAD",
|
||||
"body": {
|
||||
"y": 0.48852682
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "CONSTANT",
|
||||
"body": {
|
||||
"y": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 16,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"y": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 16,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"z": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"pitch": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 7,
|
||||
"easing": "OUTQUAD",
|
||||
"body": {
|
||||
"pitch": -2.8362873
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "CONSTANT",
|
||||
"body": {
|
||||
"pitch": -6.2831855
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 16,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"pitch": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"yaw": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 16,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"yaw": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"roll": -0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 16,
|
||||
"easing": "INQUAD",
|
||||
"body": {
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "INOUTQUAD",
|
||||
"body": {
|
||||
"bend": -0.0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,825 @@
|
||||
{
|
||||
"version": 3,
|
||||
"name": "Get the Griddy",
|
||||
"description": "fortnite battlepass",
|
||||
"author": "SeroRonin",
|
||||
"emote": {
|
||||
"beginTick": 0,
|
||||
"endTick": 91,
|
||||
"stopTick": 91,
|
||||
"isLoop": true,
|
||||
"returnTick": 2,
|
||||
"easeBeforeKeyframe": false,
|
||||
"degrees": false,
|
||||
"moves": [
|
||||
{
|
||||
"tick": 1,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.261799,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
},
|
||||
"rightArm": {
|
||||
"pitch": -0.872665,
|
||||
"yaw": -0.436332,
|
||||
"roll": 0.349066
|
||||
},
|
||||
"leftLeg": {
|
||||
"x": 1.9,
|
||||
"y": 12.0,
|
||||
"z": -0.1,
|
||||
"pitch": -0.523599,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0,
|
||||
"bend": 0.436332
|
||||
},
|
||||
"rightLeg": {
|
||||
"x": -1.9,
|
||||
"y": 12.0,
|
||||
"z": 0.1,
|
||||
"pitch": -0.174533,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0,
|
||||
"bend": 0.523599
|
||||
},
|
||||
"leftArm": {
|
||||
"x": 5.0,
|
||||
"y": 2.0,
|
||||
"z": 0.0,
|
||||
"pitch": -0.872674,
|
||||
"yaw": 0.436332,
|
||||
"roll": -0.349066
|
||||
},
|
||||
"body": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.077608,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.395397
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 4,
|
||||
"easing": "LINEAR",
|
||||
"leftLeg": {
|
||||
"pitch": -0.610865
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "LINEAR",
|
||||
"body": {
|
||||
"x": 0.0,
|
||||
"y": 0.0375,
|
||||
"z": 0.0,
|
||||
"pitch": 0.033951,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.377793
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 6,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"pitch": 0.191986
|
||||
},
|
||||
"leftLeg": {
|
||||
"bend": 0.959931
|
||||
},
|
||||
"rightLeg": {
|
||||
"bend": 0.523599
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 9,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.261799,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
},
|
||||
"rightArm": {
|
||||
"pitch": 0.523599,
|
||||
"yaw": -0.436332,
|
||||
"roll": 0.226893
|
||||
},
|
||||
"leftLeg": {
|
||||
"pitch": -0.178507,
|
||||
"bend": 0.523599
|
||||
},
|
||||
"rightLeg": {
|
||||
"pitch": -0.523599,
|
||||
"bend": 0.436332
|
||||
},
|
||||
"leftArm": {
|
||||
"x": 5.0,
|
||||
"y": 2.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.52359,
|
||||
"yaw": 0.436332,
|
||||
"roll": -0.226893
|
||||
},
|
||||
"body": {
|
||||
"y": 0.0,
|
||||
"pitch": 0.077608
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.395397
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 12,
|
||||
"easing": "LINEAR",
|
||||
"rightLeg": {
|
||||
"x": -1.9,
|
||||
"y": 12.0,
|
||||
"z": 0.1,
|
||||
"pitch": -0.610865,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
},
|
||||
"body": {
|
||||
"y": 0.0375,
|
||||
"pitch": 0.033951
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.377793
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 13,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"pitch": 0.191986
|
||||
},
|
||||
"leftLeg": {
|
||||
"bend": 0.523599
|
||||
},
|
||||
"rightLeg": {
|
||||
"bend": 0.959931
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 16,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"pitch": 0.261799,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
},
|
||||
"rightArm": {
|
||||
"pitch": -0.872665,
|
||||
"roll": 0.349066
|
||||
},
|
||||
"leftLeg": {
|
||||
"x": 1.9,
|
||||
"y": 12.0,
|
||||
"z": -0.1,
|
||||
"pitch": -0.523599,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0,
|
||||
"bend": 0.436332
|
||||
},
|
||||
"rightLeg": {
|
||||
"pitch": -0.174533,
|
||||
"bend": 0.523599
|
||||
},
|
||||
"leftArm": {
|
||||
"pitch": -0.872674,
|
||||
"roll": -0.349066
|
||||
},
|
||||
"body": {
|
||||
"y": 0.0,
|
||||
"pitch": 0.077608
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.395397
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 19,
|
||||
"easing": "LINEAR",
|
||||
"leftLeg": {
|
||||
"pitch": -0.610865
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "LINEAR",
|
||||
"body": {
|
||||
"y": 0.0375,
|
||||
"pitch": 0.033951
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.377793
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 21,
|
||||
"easing": "LINEAR",
|
||||
"leftLeg": {
|
||||
"bend": 0.959931
|
||||
},
|
||||
"rightLeg": {
|
||||
"bend": 0.523599
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 24,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"pitch": -0.416877,
|
||||
"yaw": -0.519655,
|
||||
"roll": 0.21801
|
||||
},
|
||||
"rightArm": {
|
||||
"pitch": 0.523599,
|
||||
"roll": 0.226893
|
||||
},
|
||||
"leftLeg": {
|
||||
"pitch": -0.178507,
|
||||
"bend": 0.523599
|
||||
},
|
||||
"rightLeg": {
|
||||
"pitch": -0.523599,
|
||||
"bend": 0.436332
|
||||
},
|
||||
"leftArm": {
|
||||
"pitch": 0.52359,
|
||||
"roll": -0.226893
|
||||
},
|
||||
"body": {
|
||||
"y": 0.0,
|
||||
"pitch": 0.077608
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.395397
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 27,
|
||||
"easing": "LINEAR",
|
||||
"rightLeg": {
|
||||
"pitch": -0.610865
|
||||
},
|
||||
"body": {
|
||||
"y": 0.0375,
|
||||
"pitch": 0.033951
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.377793
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 28,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"pitch": -0.457451,
|
||||
"roll": 0.21801
|
||||
},
|
||||
"leftLeg": {
|
||||
"bend": 0.523599
|
||||
},
|
||||
"rightLeg": {
|
||||
"bend": 0.959931
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 31,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"pitch": -0.416877,
|
||||
"roll": 0.21801
|
||||
},
|
||||
"rightArm": {
|
||||
"pitch": -0.872665,
|
||||
"roll": 0.349066
|
||||
},
|
||||
"leftLeg": {
|
||||
"pitch": -0.523599,
|
||||
"bend": 0.436332
|
||||
},
|
||||
"rightLeg": {
|
||||
"pitch": -0.174533,
|
||||
"bend": 0.523599
|
||||
},
|
||||
"leftArm": {
|
||||
"pitch": -0.872674,
|
||||
"roll": -0.349066
|
||||
},
|
||||
"body": {
|
||||
"y": 0.0,
|
||||
"pitch": 0.077608
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.395397
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 34,
|
||||
"easing": "LINEAR",
|
||||
"leftLeg": {
|
||||
"pitch": -0.610865
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 35,
|
||||
"easing": "LINEAR",
|
||||
"body": {
|
||||
"y": 0.0375,
|
||||
"pitch": 0.033951
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.377793
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 36,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"pitch": -0.457451,
|
||||
"roll": 0.21801
|
||||
},
|
||||
"leftLeg": {
|
||||
"bend": 0.959931
|
||||
},
|
||||
"rightLeg": {
|
||||
"bend": 0.523599
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 39,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"pitch": -0.416877,
|
||||
"roll": 0.21801
|
||||
},
|
||||
"rightArm": {
|
||||
"pitch": 0.523599,
|
||||
"roll": 0.226893
|
||||
},
|
||||
"leftLeg": {
|
||||
"pitch": -0.178507,
|
||||
"bend": 0.523599
|
||||
},
|
||||
"rightLeg": {
|
||||
"pitch": -0.523599,
|
||||
"bend": 0.436332
|
||||
},
|
||||
"leftArm": {
|
||||
"pitch": 0.52359,
|
||||
"roll": -0.226893
|
||||
},
|
||||
"body": {
|
||||
"y": 0.0,
|
||||
"pitch": 0.077608
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.395397
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 42,
|
||||
"easing": "LINEAR",
|
||||
"rightLeg": {
|
||||
"pitch": -0.610865
|
||||
},
|
||||
"body": {
|
||||
"y": 0.0375,
|
||||
"pitch": 0.033951
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.377793
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 43,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"pitch": -0.457451,
|
||||
"roll": 0.21801
|
||||
},
|
||||
"leftLeg": {
|
||||
"bend": 0.523599
|
||||
},
|
||||
"rightLeg": {
|
||||
"bend": 0.959931
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 46,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"pitch": -0.416877,
|
||||
"yaw": -0.519655,
|
||||
"roll": 0.21801
|
||||
},
|
||||
"rightArm": {
|
||||
"pitch": -0.872665,
|
||||
"roll": 0.349066,
|
||||
"bend": 0.0
|
||||
},
|
||||
"leftLeg": {
|
||||
"pitch": -0.523599,
|
||||
"bend": 0.436332
|
||||
},
|
||||
"rightLeg": {
|
||||
"pitch": -0.174533,
|
||||
"bend": 0.523599
|
||||
},
|
||||
"leftArm": {
|
||||
"pitch": -0.872674,
|
||||
"roll": -0.349066,
|
||||
"bend": 0.0
|
||||
},
|
||||
"body": {
|
||||
"y": 0.0,
|
||||
"pitch": 0.077608
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.395397
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 49,
|
||||
"easing": "LINEAR",
|
||||
"leftLeg": {
|
||||
"pitch": -0.610865
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 50,
|
||||
"easing": "LINEAR",
|
||||
"body": {
|
||||
"y": 0.0375,
|
||||
"pitch": 0.033951
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.377793
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 51,
|
||||
"easing": "LINEAR",
|
||||
"leftLeg": {
|
||||
"bend": 0.959931
|
||||
},
|
||||
"rightLeg": {
|
||||
"bend": 0.523599
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 54,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"pitch": -0.000488,
|
||||
"yaw": -0.272548,
|
||||
"roll": -0.027396
|
||||
},
|
||||
"rightArm": {
|
||||
"pitch": 0.523599,
|
||||
"roll": 0.226893,
|
||||
"bend": -0.872665
|
||||
},
|
||||
"leftLeg": {
|
||||
"pitch": -0.178507,
|
||||
"bend": 0.523599
|
||||
},
|
||||
"rightLeg": {
|
||||
"pitch": -0.523599,
|
||||
"bend": 0.436332
|
||||
},
|
||||
"leftArm": {
|
||||
"pitch": 0.52359,
|
||||
"roll": -0.226893,
|
||||
"bend": -0.872665
|
||||
},
|
||||
"body": {
|
||||
"y": 0.0,
|
||||
"pitch": 0.077608
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.395397
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 57,
|
||||
"easing": "LINEAR",
|
||||
"rightLeg": {
|
||||
"pitch": -0.610865
|
||||
},
|
||||
"body": {
|
||||
"y": 0.0375,
|
||||
"pitch": 0.033951
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.377793
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 58,
|
||||
"easing": "LINEAR",
|
||||
"leftLeg": {
|
||||
"bend": 0.523599
|
||||
},
|
||||
"rightLeg": {
|
||||
"bend": 0.959931
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 61,
|
||||
"easing": "LINEAR",
|
||||
"rightArm": {
|
||||
"pitch": -0.872665,
|
||||
"yaw": -0.436332,
|
||||
"roll": 0.349066
|
||||
},
|
||||
"leftLeg": {
|
||||
"pitch": -0.523599,
|
||||
"bend": 0.436332
|
||||
},
|
||||
"rightLeg": {
|
||||
"pitch": -0.174533,
|
||||
"bend": 0.523599
|
||||
},
|
||||
"leftArm": {
|
||||
"pitch": -0.872674,
|
||||
"yaw": 0.436332,
|
||||
"roll": -0.349066
|
||||
},
|
||||
"body": {
|
||||
"y": 0.0,
|
||||
"pitch": 0.077608
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.395397
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 64,
|
||||
"easing": "LINEAR",
|
||||
"leftLeg": {
|
||||
"pitch": -0.610865
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 65,
|
||||
"easing": "LINEAR",
|
||||
"body": {
|
||||
"y": 0.0375,
|
||||
"pitch": 0.033951
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.377793
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 66,
|
||||
"easing": "LINEAR",
|
||||
"leftLeg": {
|
||||
"bend": 0.959931
|
||||
},
|
||||
"rightLeg": {
|
||||
"bend": 0.523599
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 69,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"pitch": -0.523599,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
},
|
||||
"rightArm": {
|
||||
"pitch": -2.356194,
|
||||
"yaw": 0.087266,
|
||||
"roll": 0.523599
|
||||
},
|
||||
"leftLeg": {
|
||||
"pitch": -0.178507,
|
||||
"bend": 0.523599
|
||||
},
|
||||
"rightLeg": {
|
||||
"pitch": -0.523599,
|
||||
"bend": 0.436332
|
||||
},
|
||||
"leftArm": {
|
||||
"pitch": -2.338528,
|
||||
"yaw": -0.087266,
|
||||
"roll": -0.523599
|
||||
},
|
||||
"body": {
|
||||
"y": 0.0,
|
||||
"pitch": 0.077608
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.395397
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 72,
|
||||
"easing": "LINEAR",
|
||||
"rightLeg": {
|
||||
"pitch": -0.610865
|
||||
},
|
||||
"body": {
|
||||
"y": 0.0375,
|
||||
"pitch": 0.033951
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.377793
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 73,
|
||||
"easing": "LINEAR",
|
||||
"leftLeg": {
|
||||
"bend": 0.523599
|
||||
},
|
||||
"rightLeg": {
|
||||
"bend": 0.959931
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 76,
|
||||
"easing": "LINEAR",
|
||||
"rightArm": {
|
||||
"bend": -0.959931
|
||||
},
|
||||
"leftLeg": {
|
||||
"pitch": -0.523599,
|
||||
"bend": 0.436332
|
||||
},
|
||||
"rightLeg": {
|
||||
"pitch": -0.174533,
|
||||
"bend": 0.523599
|
||||
},
|
||||
"leftArm": {
|
||||
"bend": -0.959931
|
||||
},
|
||||
"body": {
|
||||
"y": 0.0,
|
||||
"pitch": 0.077608
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.395397
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 79,
|
||||
"easing": "LINEAR",
|
||||
"leftLeg": {
|
||||
"pitch": -0.610865
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "LINEAR",
|
||||
"body": {
|
||||
"y": 0.0375,
|
||||
"pitch": 0.033951
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.377793
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 81,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0,
|
||||
"pitch": -0.523599,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
},
|
||||
"rightArm": {
|
||||
"pitch": -2.181662,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.436332
|
||||
},
|
||||
"leftLeg": {
|
||||
"bend": 0.959931
|
||||
},
|
||||
"rightLeg": {
|
||||
"bend": 0.523599
|
||||
},
|
||||
"leftArm": {
|
||||
"pitch": -2.185317,
|
||||
"yaw": 0.0,
|
||||
"roll": -0.436332
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 84,
|
||||
"easing": "LINEAR",
|
||||
"leftLeg": {
|
||||
"pitch": -0.178507,
|
||||
"bend": 0.523599
|
||||
},
|
||||
"rightLeg": {
|
||||
"pitch": -0.523599,
|
||||
"bend": 0.436332
|
||||
},
|
||||
"body": {
|
||||
"y": 0.0,
|
||||
"pitch": 0.077608
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.395397
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 87,
|
||||
"easing": "LINEAR",
|
||||
"rightLeg": {
|
||||
"pitch": -0.610865
|
||||
},
|
||||
"body": {
|
||||
"y": 0.0375,
|
||||
"pitch": 0.033951
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.377793
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 88,
|
||||
"easing": "LINEAR",
|
||||
"leftLeg": {
|
||||
"bend": 0.523599
|
||||
},
|
||||
"rightLeg": {
|
||||
"bend": 0.959931
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 91,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"pitch": 0.261799
|
||||
},
|
||||
"rightArm": {
|
||||
"pitch": -0.872665,
|
||||
"yaw": -0.436332,
|
||||
"roll": 0.349066,
|
||||
"bend": 0.0
|
||||
},
|
||||
"leftLeg": {
|
||||
"x": 1.9,
|
||||
"y": 12.0,
|
||||
"z": -0.1,
|
||||
"pitch": -0.523599,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0,
|
||||
"bend": 0.436332
|
||||
},
|
||||
"rightLeg": {
|
||||
"x": -1.9,
|
||||
"y": 12.0,
|
||||
"z": 0.1,
|
||||
"pitch": -0.174533,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0,
|
||||
"bend": 0.523599
|
||||
},
|
||||
"leftArm": {
|
||||
"x": 5.0,
|
||||
"y": 2.0,
|
||||
"z": 0.0,
|
||||
"pitch": -0.872674,
|
||||
"yaw": 0.436332,
|
||||
"roll": -0.349066,
|
||||
"bend": 0.0
|
||||
},
|
||||
"body": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.077608,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
},
|
||||
"torso": {
|
||||
"bend": 0.395397
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
{
|
||||
"version": 3,
|
||||
"name": "Salute",
|
||||
"description": "Hand behind back",
|
||||
"author": "Wah0o",
|
||||
"emote": {
|
||||
"beginTick": 0,
|
||||
"endTick": 30,
|
||||
"stopTick": 30,
|
||||
"isLoop": true,
|
||||
"returnTick": 12,
|
||||
"easeBeforeKeyframe": false,
|
||||
"degrees": false,
|
||||
"moves": [
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"head": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
},
|
||||
"body": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
},
|
||||
"rightArm": {
|
||||
"x": -5.0,
|
||||
"y": 2.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.484846,
|
||||
"yaw": 0.0,
|
||||
"roll": -0.576996
|
||||
},
|
||||
"leftArm": {
|
||||
"x": 5.0,
|
||||
"y": 2.0,
|
||||
"z": 0.0,
|
||||
"pitch": -2.604544,
|
||||
"yaw": 0.0,
|
||||
"roll": -0.400639
|
||||
},
|
||||
"rightLeg": {
|
||||
"x": -1.9,
|
||||
"y": 12.0,
|
||||
"z": 0.1,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
},
|
||||
"leftLeg": {
|
||||
"x": 1.9,
|
||||
"y": 12.0,
|
||||
"z": -0.1,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 30,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"head": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
},
|
||||
"body": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
},
|
||||
"rightArm": {
|
||||
"x": -5.0,
|
||||
"y": 2.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.484846,
|
||||
"yaw": 0.0,
|
||||
"roll": -0.576996
|
||||
},
|
||||
"leftArm": {
|
||||
"x": 5.0,
|
||||
"y": 2.0,
|
||||
"z": 0.0,
|
||||
"pitch": -2.604544,
|
||||
"yaw": 0.0,
|
||||
"roll": -0.400639
|
||||
},
|
||||
"rightLeg": {
|
||||
"x": -1.9,
|
||||
"y": 12.0,
|
||||
"z": 0.1,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
},
|
||||
"leftLeg": {
|
||||
"x": 1.9,
|
||||
"y": 12.0,
|
||||
"z": -0.1,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"version": 3,
|
||||
"name": "Shrug",
|
||||
"description": "don't care",
|
||||
"author": "Wah0o",
|
||||
"emote": {
|
||||
"beginTick": 0,
|
||||
"endTick": 30,
|
||||
"stopTick": 31,
|
||||
"isLoop": false,
|
||||
"returnTick": 1,
|
||||
"easeBeforeKeyframe": false,
|
||||
"degrees": false,
|
||||
"moves": [
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"head": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.537013,
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"head": {
|
||||
"yaw": -0.449322
|
||||
},
|
||||
"rightArm": {
|
||||
"x": -5.0,
|
||||
"y": 2.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
},
|
||||
"leftArm": {
|
||||
"x": 5.0,
|
||||
"y": 2.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 25,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"rightArm": {
|
||||
"y": 0.673667
|
||||
},
|
||||
"leftArm": {
|
||||
"y": 0.738915
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 30,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"head": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
},
|
||||
"rightArm": {
|
||||
"x": -5.0,
|
||||
"y": 2.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
},
|
||||
"leftArm": {
|
||||
"x": 5.0,
|
||||
"y": 2.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
{
|
||||
"version": 3,
|
||||
"name": "Take The L",
|
||||
"description": "Savor the W",
|
||||
"author": "Landro",
|
||||
"emote": {
|
||||
"beginTick": 0,
|
||||
"endTick": 30,
|
||||
"stopTick": 30,
|
||||
"isLoop": true,
|
||||
"returnTick": 10,
|
||||
"easeBeforeKeyframe": false,
|
||||
"degrees": false,
|
||||
"moves": [
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"head": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
},
|
||||
"body": {
|
||||
"y": 0.25,
|
||||
"roll": 0.0
|
||||
},
|
||||
"leftArm": {
|
||||
"x": 5.76,
|
||||
"y": 3.28,
|
||||
"z": -0.64,
|
||||
"pitch": -1.294932,
|
||||
"yaw": 0.991577,
|
||||
"roll": -1.205874,
|
||||
"bend": -1.057819
|
||||
},
|
||||
"rightLeg": {
|
||||
"x": -1.9,
|
||||
"y": 12.0,
|
||||
"z": 0.1,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.261799,
|
||||
"roll": 0.0,
|
||||
"bend": 0.0
|
||||
},
|
||||
"leftLeg": {
|
||||
"x": 1.9,
|
||||
"y": 12.0,
|
||||
"z": -0.1,
|
||||
"pitch": 0.0,
|
||||
"yaw": -0.523599,
|
||||
"roll": -0.785398
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 8,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"leftLeg": {
|
||||
"x": 2.9,
|
||||
"y": 11.0,
|
||||
"z": -1.1,
|
||||
"yaw": -0.959931,
|
||||
"roll": -1.396263,
|
||||
"bend": 0.785398
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"head": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.030844,
|
||||
"yaw": 0.0,
|
||||
"roll": -0.059719
|
||||
},
|
||||
"body": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"roll": -0.087266
|
||||
},
|
||||
"rightArm": {
|
||||
"x": -7.0,
|
||||
"y": -2.0,
|
||||
"z": -2.0,
|
||||
"pitch": -1.416631,
|
||||
"yaw": -0.785398,
|
||||
"roll": 4.110391,
|
||||
"bend": 1.308997
|
||||
},
|
||||
"leftArm": {
|
||||
"x": 5.76,
|
||||
"y": 3.28,
|
||||
"z": -0.64,
|
||||
"pitch": -1.294932,
|
||||
"yaw": 0.991577,
|
||||
"roll": -1.205874,
|
||||
"bend": -1.057819
|
||||
},
|
||||
"rightLeg": {
|
||||
"x": -1.9,
|
||||
"y": 12.0,
|
||||
"z": -0.9,
|
||||
"pitch": -0.174533,
|
||||
"yaw": 0.261799,
|
||||
"roll": 0.174533,
|
||||
"bend": 0.261799
|
||||
},
|
||||
"leftLeg": {
|
||||
"x": 2.9,
|
||||
"y": 11.0,
|
||||
"z": -1.1,
|
||||
"pitch": 0.0,
|
||||
"yaw": -0.959931,
|
||||
"roll": -1.570796,
|
||||
"bend": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 12,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"leftLeg": {
|
||||
"bend": 0.785398
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 15,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
},
|
||||
"body": {
|
||||
"y": 0.25,
|
||||
"roll": 0.0
|
||||
},
|
||||
"rightLeg": {
|
||||
"x": -1.9,
|
||||
"y": 12.0,
|
||||
"z": 0.1,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.261799,
|
||||
"roll": 0.0,
|
||||
"bend": 0.0
|
||||
},
|
||||
"leftLeg": {
|
||||
"x": 1.9,
|
||||
"y": 12.0,
|
||||
"z": -0.1,
|
||||
"pitch": 0.0,
|
||||
"yaw": -0.261799,
|
||||
"roll": 0.0,
|
||||
"bend": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 18,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"rightLeg": {
|
||||
"x": -2.9,
|
||||
"y": 11.0,
|
||||
"z": -0.9,
|
||||
"yaw": 0.959931,
|
||||
"roll": 1.396263,
|
||||
"bend": 0.785398
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 20,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.105365,
|
||||
"yaw": 0.000794,
|
||||
"roll": 0.055846
|
||||
},
|
||||
"body": {
|
||||
"y": 0.0,
|
||||
"roll": 0.087266
|
||||
},
|
||||
"rightArm": {
|
||||
"pitch": -1.132143
|
||||
},
|
||||
"rightLeg": {
|
||||
"x": -2.9,
|
||||
"y": 11.0,
|
||||
"z": -0.9,
|
||||
"yaw": 0.959931,
|
||||
"roll": 1.570796,
|
||||
"bend": 0.0
|
||||
},
|
||||
"leftLeg": {
|
||||
"pitch": -0.174533,
|
||||
"roll": -0.174533,
|
||||
"bend": 0.261799
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 22,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"rightLeg": {
|
||||
"bend": 0.785398
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 25,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
},
|
||||
"body": {
|
||||
"y": 0.25
|
||||
},
|
||||
"rightLeg": {
|
||||
"x": -1.9,
|
||||
"y": 12.0,
|
||||
"z": 0.1,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.261799,
|
||||
"roll": 0.0,
|
||||
"bend": 0.0
|
||||
},
|
||||
"leftLeg": {
|
||||
"x": 1.9,
|
||||
"y": 12.0,
|
||||
"z": -0.1,
|
||||
"pitch": 0.0,
|
||||
"yaw": -0.261799,
|
||||
"roll": 0.0,
|
||||
"bend": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 28,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"leftLeg": {
|
||||
"x": 2.9,
|
||||
"y": 11.0,
|
||||
"z": -1.1,
|
||||
"yaw": -0.959931,
|
||||
"roll": -1.396263,
|
||||
"bend": 0.785398
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 30,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"head": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.030844,
|
||||
"yaw": 0.0,
|
||||
"roll": -0.059719
|
||||
},
|
||||
"body": {
|
||||
"y": 0.0,
|
||||
"roll": -0.087266
|
||||
},
|
||||
"rightArm": {
|
||||
"x": -7.0,
|
||||
"y": -2.0,
|
||||
"z": -2.0,
|
||||
"pitch": -1.416631,
|
||||
"yaw": -0.785398,
|
||||
"roll": 4.110391,
|
||||
"bend": 1.308997
|
||||
},
|
||||
"leftArm": {
|
||||
"x": 5.76,
|
||||
"y": 3.28,
|
||||
"z": -0.64,
|
||||
"pitch": -1.294932,
|
||||
"yaw": 0.991577,
|
||||
"roll": -1.205874,
|
||||
"bend": -1.057819
|
||||
},
|
||||
"rightLeg": {
|
||||
"x": -1.9,
|
||||
"y": 12.0,
|
||||
"z": -0.9,
|
||||
"pitch": -0.174533,
|
||||
"yaw": 0.261799,
|
||||
"roll": 0.174533,
|
||||
"bend": 0.261799
|
||||
},
|
||||
"leftLeg": {
|
||||
"x": 2.9,
|
||||
"y": 11.0,
|
||||
"z": -1.1,
|
||||
"pitch": 0.0,
|
||||
"yaw": -0.959931,
|
||||
"roll": -1.570796,
|
||||
"bend": 0.0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,685 @@
|
||||
{
|
||||
"version": 3,
|
||||
"name": "Twerk",
|
||||
"description": "Hips back, and bounce",
|
||||
"author": "Saturn Client",
|
||||
"emote": {
|
||||
"degrees": true,
|
||||
"isLoop": true,
|
||||
"returnTick": 0,
|
||||
"endTick": 12,
|
||||
"stopTick": 13,
|
||||
"moves": [
|
||||
{
|
||||
"tick": 0,
|
||||
"easing": "LINEAR",
|
||||
"body": {
|
||||
"x": -0.0,
|
||||
"y": 0.0637
|
||||
},
|
||||
"head": {
|
||||
"x": 0.1873,
|
||||
"y": 7.6654,
|
||||
"z": -3.9561,
|
||||
"pitch": 28.4994,
|
||||
"yaw": 0.5013,
|
||||
"roll": -0.9013
|
||||
},
|
||||
"torso": {
|
||||
"x": 0.1873,
|
||||
"y": 7.6654,
|
||||
"z": -3.9561,
|
||||
"pitch": 43.8857,
|
||||
"yaw": -0.2748,
|
||||
"roll": -0.5704,
|
||||
"bend": 32.4225,
|
||||
"axis": 178.4861
|
||||
},
|
||||
"rightArm": {
|
||||
"x": -4.8056,
|
||||
"y": 9.1559,
|
||||
"z": -2.5937,
|
||||
"pitch": 41.2104,
|
||||
"yaw": 11.1599,
|
||||
"roll": 11.5835,
|
||||
"bend": -28.9106,
|
||||
"axis": -6.4245
|
||||
},
|
||||
"leftArm": {
|
||||
"x": 5.1932,
|
||||
"y": 9.0568,
|
||||
"z": -2.5457,
|
||||
"pitch": 41.2134,
|
||||
"yaw": -10.0744,
|
||||
"roll": -11.3678,
|
||||
"bend": -34.2847,
|
||||
"axis": 3.7104
|
||||
},
|
||||
"rightLeg": {
|
||||
"x": -1.6738,
|
||||
"y": 16.311,
|
||||
"z": 4.4622,
|
||||
"pitch": -81.0086,
|
||||
"yaw": 25.937,
|
||||
"roll": 11.6147,
|
||||
"bend": -106.5341,
|
||||
"axis": 178.6748
|
||||
},
|
||||
"leftLeg": {
|
||||
"x": 2.1262,
|
||||
"y": 16.311,
|
||||
"z": 4.4622,
|
||||
"pitch": -80.1541,
|
||||
"yaw": -23.1741,
|
||||
"roll": -15.9396,
|
||||
"bend": -109.5512,
|
||||
"axis": 186.6826
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 1,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"y": 7.3213,
|
||||
"z": -3.9546
|
||||
},
|
||||
"torso": {
|
||||
"y": 7.3213,
|
||||
"z": -3.9546,
|
||||
"pitch": 44.2163,
|
||||
"roll": -1.2208,
|
||||
"bend": 31.983
|
||||
},
|
||||
"rightArm": {
|
||||
"y": 8.8595,
|
||||
"z": -2.4569,
|
||||
"pitch": 40.8328,
|
||||
"roll": 6.2457,
|
||||
"bend": -30.0698,
|
||||
"axis": -6.2466
|
||||
},
|
||||
"leftArm": {
|
||||
"y": 8.6462,
|
||||
"z": -2.6643,
|
||||
"pitch": 39.718,
|
||||
"roll": -11.4772,
|
||||
"bend": -31.5341,
|
||||
"axis": 5.9068
|
||||
},
|
||||
"rightLeg": {
|
||||
"y": 15.9102,
|
||||
"pitch": -75.926,
|
||||
"yaw": 24.5861,
|
||||
"roll": 13.5142,
|
||||
"bend": -102.1127
|
||||
},
|
||||
"leftLeg": {
|
||||
"y": 15.9102,
|
||||
"pitch": -75.5106,
|
||||
"yaw": -27.5028,
|
||||
"roll": -13.7351,
|
||||
"bend": -102.8428,
|
||||
"axis": 189.6313
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 2,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"x": -0.8055,
|
||||
"y": 8.1689
|
||||
},
|
||||
"torso": {
|
||||
"x": -0.8055,
|
||||
"y": 8.1689,
|
||||
"bend": 38.1002
|
||||
},
|
||||
"rightArm": {
|
||||
"x": -5.6879,
|
||||
"y": 9.7845,
|
||||
"axis": -3.7942
|
||||
},
|
||||
"leftArm": {
|
||||
"x": 4.2976,
|
||||
"y": 9.2523,
|
||||
"pitch": 44.6401,
|
||||
"yaw": -6.835,
|
||||
"roll": -16.9446,
|
||||
"bend": -32.4677
|
||||
},
|
||||
"rightLeg": {
|
||||
"y": 16.2659,
|
||||
"pitch": -78.5245,
|
||||
"yaw": 20.2182,
|
||||
"roll": 19.5894,
|
||||
"bend": -107.209
|
||||
},
|
||||
"leftLeg": {
|
||||
"y": 16.2659,
|
||||
"pitch": -79.0541,
|
||||
"bend": -104.5866
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 3,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"x": -1.5256,
|
||||
"y": 9.7364,
|
||||
"z": -4.854,
|
||||
"pitch": 34.1426
|
||||
},
|
||||
"torso": {
|
||||
"x": -1.5256,
|
||||
"y": 9.7364,
|
||||
"z": -4.854,
|
||||
"pitch": 51.4994,
|
||||
"roll": -5.723,
|
||||
"bend": 46.2958,
|
||||
"axis": 171.0835
|
||||
},
|
||||
"rightArm": {
|
||||
"x": -6.3053,
|
||||
"y": 11.4659,
|
||||
"z": -3.0779,
|
||||
"pitch": 49.8778,
|
||||
"yaw": 10.9541,
|
||||
"roll": 9.4174,
|
||||
"bend": -29.3297
|
||||
},
|
||||
"leftArm": {
|
||||
"x": 3.6347,
|
||||
"y": 10.4699,
|
||||
"z": -3.5025,
|
||||
"pitch": 51.8029,
|
||||
"roll": -24.6351,
|
||||
"axis": 3.6087
|
||||
},
|
||||
"rightLeg": {
|
||||
"z": 4.6288,
|
||||
"yaw": 18.5052,
|
||||
"roll": 21.802,
|
||||
"bend": -116.6488,
|
||||
"axis": 171.6123
|
||||
},
|
||||
"leftLeg": {
|
||||
"z": 4.6288,
|
||||
"pitch": -87.1428,
|
||||
"yaw": -23.4405,
|
||||
"axis": 180.9108
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 4,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"x": -1.8731,
|
||||
"y": 10.6957,
|
||||
"z": -5.0099,
|
||||
"pitch": 33.2442,
|
||||
"yaw": 3.9201,
|
||||
"roll": -6.0227
|
||||
},
|
||||
"torso": {
|
||||
"x": -1.8731,
|
||||
"y": 10.6957,
|
||||
"z": -5.0099,
|
||||
"pitch": 52.4228,
|
||||
"yaw": 3.8511,
|
||||
"roll": -5.9949,
|
||||
"bend": 49.0159,
|
||||
"axis": 172.2434
|
||||
},
|
||||
"rightArm": {
|
||||
"x": -6.5993,
|
||||
"y": 12.418,
|
||||
"z": -3.0931,
|
||||
"pitch": 51.1349,
|
||||
"yaw": 8.0105,
|
||||
"axis": -8.2902
|
||||
},
|
||||
"leftArm": {
|
||||
"x": 3.3186,
|
||||
"y": 11.3757,
|
||||
"z": -3.7647,
|
||||
"pitch": 53.8923,
|
||||
"yaw": -6.0212,
|
||||
"roll": -27.197,
|
||||
"bend": -39.6549,
|
||||
"axis": 5.9148
|
||||
},
|
||||
"rightLeg": {
|
||||
"x": -2.3763,
|
||||
"y": 17.9026,
|
||||
"pitch": -93.1557,
|
||||
"bend": -122.9048,
|
||||
"axis": 170.6601
|
||||
},
|
||||
"leftLeg": {
|
||||
"x": 1.4237,
|
||||
"y": 17.9026,
|
||||
"pitch": -93.1369,
|
||||
"bend": -121.5363
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 5,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"x": -1.4047,
|
||||
"y": 9.9389,
|
||||
"pitch": 28.9007,
|
||||
"yaw": 3.2148
|
||||
},
|
||||
"torso": {
|
||||
"x": -1.4047,
|
||||
"y": 9.9389,
|
||||
"yaw": 3.7305,
|
||||
"bend": 43.4407
|
||||
},
|
||||
"rightArm": {
|
||||
"x": -6.2198,
|
||||
"y": 11.5114,
|
||||
"pitch": 48.4737,
|
||||
"yaw": 7.9932,
|
||||
"bend": -38.6811
|
||||
},
|
||||
"leftArm": {
|
||||
"x": 3.7446,
|
||||
"y": 10.9738,
|
||||
"z": -3.474,
|
||||
"roll": -23.505,
|
||||
"bend": -36.714
|
||||
},
|
||||
"rightLeg": {
|
||||
"y": 17.761,
|
||||
"pitch": -92.4411,
|
||||
"roll": 20.4558,
|
||||
"bend": -121.9263
|
||||
},
|
||||
"leftLeg": {
|
||||
"y": 17.761,
|
||||
"pitch": -92.379,
|
||||
"yaw": -26.2774,
|
||||
"roll": -17.4598,
|
||||
"bend": -120.6269,
|
||||
"axis": 184.7551
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 6,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"x": -0.5445,
|
||||
"y": 8.2919,
|
||||
"z": -4.1286,
|
||||
"pitch": 27.4449
|
||||
},
|
||||
"torso": {
|
||||
"x": -0.5445,
|
||||
"y": 8.2919,
|
||||
"z": -4.1286,
|
||||
"pitch": 44.9959,
|
||||
"roll": -0.1331,
|
||||
"bend": 35.1937
|
||||
},
|
||||
"rightArm": {
|
||||
"x": -5.5003,
|
||||
"y": 9.7189,
|
||||
"z": -2.5814,
|
||||
"yaw": 10.117,
|
||||
"roll": 11.6263,
|
||||
"bend": -36.6136,
|
||||
"axis": -2.9818
|
||||
},
|
||||
"leftArm": {
|
||||
"x": 4.4989,
|
||||
"y": 9.6953,
|
||||
"z": -2.8477,
|
||||
"pitch": 43.3362,
|
||||
"yaw": -10.8396,
|
||||
"bend": -30.325
|
||||
},
|
||||
"rightLeg": {
|
||||
"y": 16.7832,
|
||||
"z": 4.4556,
|
||||
"yaw": 20.579,
|
||||
"axis": 174.8949
|
||||
},
|
||||
"leftLeg": {
|
||||
"y": 16.7832,
|
||||
"z": 4.4556,
|
||||
"roll": -13.3635,
|
||||
"axis": 181.9013
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 7,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"y": 7.335,
|
||||
"z": -3.8955,
|
||||
"yaw": -1.2543,
|
||||
"roll": 1.9551
|
||||
},
|
||||
"torso": {
|
||||
"y": 7.335,
|
||||
"z": -3.8955,
|
||||
"pitch": 43.6308,
|
||||
"yaw": -0.6674,
|
||||
"bend": 31.2968
|
||||
},
|
||||
"rightArm": {
|
||||
"y": 8.7024,
|
||||
"z": -2.5743,
|
||||
"pitch": 39.7281,
|
||||
"roll": 11.0094,
|
||||
"bend": -32.4336,
|
||||
"axis": -5.1024
|
||||
},
|
||||
"leftArm": {
|
||||
"y": 8.8585,
|
||||
"z": -2.4579,
|
||||
"pitch": 40.3248,
|
||||
"roll": -8.1427,
|
||||
"bend": -29.1361,
|
||||
"axis": 6.7593
|
||||
},
|
||||
"rightLeg": {
|
||||
"x": -2.0654,
|
||||
"y": 16.0076,
|
||||
"pitch": -76.8625,
|
||||
"yaw": 25.941,
|
||||
"roll": 14.4188,
|
||||
"bend": -105.4043,
|
||||
"axis": 171.2628
|
||||
},
|
||||
"leftLeg": {
|
||||
"x": 1.7346,
|
||||
"y": 16.0076,
|
||||
"pitch": -77.6408,
|
||||
"yaw": -25.7135,
|
||||
"roll": -11.5799,
|
||||
"bend": -103.133,
|
||||
"axis": 182.0395
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 8,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"x": 0.5149,
|
||||
"y": 7.6114,
|
||||
"z": -4.1189
|
||||
},
|
||||
"torso": {
|
||||
"x": 0.5149,
|
||||
"y": 7.6114,
|
||||
"z": -4.1189,
|
||||
"pitch": 45.5567,
|
||||
"roll": 1.9,
|
||||
"bend": 34.3817,
|
||||
"axis": 185.8808
|
||||
},
|
||||
"rightArm": {
|
||||
"x": -4.5647,
|
||||
"y": 8.8445,
|
||||
"z": -2.8129,
|
||||
"pitch": 41.452,
|
||||
"yaw": 7.1089,
|
||||
"roll": 13.4728,
|
||||
"bend": -31.5905,
|
||||
"axis": -5.4945
|
||||
},
|
||||
"leftArm": {
|
||||
"y": 9.1765,
|
||||
"z": -2.5698,
|
||||
"pitch": 42.5806,
|
||||
"roll": -6.1009,
|
||||
"bend": -30.5432
|
||||
},
|
||||
"rightLeg": {
|
||||
"y": 16.0062,
|
||||
"pitch": -76.3427,
|
||||
"yaw": 27.2082,
|
||||
"roll": 13.8298,
|
||||
"bend": -102.5222,
|
||||
"axis": 171.6254
|
||||
},
|
||||
"leftLeg": {
|
||||
"y": 16.0062,
|
||||
"pitch": -76.2516,
|
||||
"bend": -103.6369
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 9,
|
||||
"easing": "LINEAR",
|
||||
"torso": {
|
||||
"axis": 188.354
|
||||
},
|
||||
"rightArm": {
|
||||
"y": 9.8245,
|
||||
"axis": -3.4265
|
||||
},
|
||||
"leftArm": {
|
||||
"yaw": -11.6764,
|
||||
"roll": -8.5612,
|
||||
"bend": -29.2921,
|
||||
"axis": 3.7762
|
||||
},
|
||||
"rightLeg": {
|
||||
"y": 16.6561,
|
||||
"z": 4.6178,
|
||||
"yaw": 23.9791,
|
||||
"bend": -108.5895,
|
||||
"axis": 177.6256
|
||||
},
|
||||
"leftLeg": {
|
||||
"y": 16.6561,
|
||||
"z": 4.6178,
|
||||
"pitch": -82.2218,
|
||||
"yaw": -18.7129,
|
||||
"roll": -21.3727
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 10,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"x": 1.7868,
|
||||
"y": 10.3947,
|
||||
"z": -4.9963,
|
||||
"pitch": 34.2919,
|
||||
"roll": 5.9583
|
||||
},
|
||||
"torso": {
|
||||
"x": 1.7868,
|
||||
"y": 10.3947,
|
||||
"z": -4.9963,
|
||||
"pitch": 52.5511,
|
||||
"roll": 6.3235,
|
||||
"bend": 48.7324,
|
||||
"axis": 188.7341
|
||||
},
|
||||
"rightArm": {
|
||||
"x": -3.3947,
|
||||
"y": 11.0427,
|
||||
"z": -3.69,
|
||||
"pitch": 53.7658,
|
||||
"yaw": 6.158,
|
||||
"roll": 26.7903,
|
||||
"bend": -38.4178
|
||||
},
|
||||
"leftArm": {
|
||||
"x": 6.5243,
|
||||
"y": 12.1424,
|
||||
"z": -3.1327,
|
||||
"pitch": 51.0522,
|
||||
"bend": -30.9765,
|
||||
"axis": 7.1451
|
||||
},
|
||||
"rightLeg": {
|
||||
"x": -1.4452,
|
||||
"y": 17.5819,
|
||||
"pitch": -90.7807,
|
||||
"bend": -118.2986,
|
||||
"axis": 178.4542
|
||||
},
|
||||
"leftLeg": {
|
||||
"x": 2.3548,
|
||||
"y": 17.5819,
|
||||
"pitch": -90.4714,
|
||||
"roll": -21.4522,
|
||||
"bend": -120.5366,
|
||||
"axis": 189.2421
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 11,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"x": 1.7369,
|
||||
"y": 10.5273,
|
||||
"z": -4.8864,
|
||||
"yaw": -3.8864,
|
||||
"roll": 5.3801
|
||||
},
|
||||
"torso": {
|
||||
"x": 1.7369,
|
||||
"y": 10.5273,
|
||||
"z": -4.8864,
|
||||
"pitch": 51.13,
|
||||
"yaw": -4.0928,
|
||||
"bend": 47.0625
|
||||
},
|
||||
"rightArm": {
|
||||
"x": -3.4451,
|
||||
"y": 11.352,
|
||||
"z": -3.6905,
|
||||
"pitch": 52.2992,
|
||||
"yaw": 6.6309,
|
||||
"roll": 26.0007,
|
||||
"bend": -39.1249,
|
||||
"axis": -6.422
|
||||
},
|
||||
"leftArm": {
|
||||
"x": 6.4911,
|
||||
"y": 12.1855,
|
||||
"pitch": 50.212,
|
||||
"yaw": -7.4219,
|
||||
"bend": -36.9681,
|
||||
"axis": 7.7245
|
||||
},
|
||||
"rightLeg": {
|
||||
"y": 17.9759,
|
||||
"pitch": -93.724,
|
||||
"yaw": 26.1749,
|
||||
"roll": 17.9629,
|
||||
"bend": -122.4062,
|
||||
"axis": 175.2086
|
||||
},
|
||||
"leftLeg": {
|
||||
"y": 17.9759,
|
||||
"pitch": -93.8978,
|
||||
"bend": -123.3927,
|
||||
"axis": 188.3051
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 12,
|
||||
"easing": "LINEAR",
|
||||
"head": {
|
||||
"pitch": 27.4876
|
||||
},
|
||||
"rightArm": {
|
||||
"y": 10.3597,
|
||||
"roll": 19.9926
|
||||
},
|
||||
"leftArm": {
|
||||
"bend": -38.4293,
|
||||
"axis": 3.7703
|
||||
},
|
||||
"rightLeg": {
|
||||
"y": 17.3175,
|
||||
"pitch": -89.3376,
|
||||
"bend": -116.6299
|
||||
},
|
||||
"leftLeg": {
|
||||
"y": 17.3175,
|
||||
"pitch": -89.0719,
|
||||
"yaw": -19.2542,
|
||||
"roll": -19.5116,
|
||||
"bend": -118.7259,
|
||||
"axis": 185.0963
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 13,
|
||||
"easing": "LINEAR",
|
||||
"body": {
|
||||
"x": -0.0,
|
||||
"y": 0.0637
|
||||
},
|
||||
"head": {
|
||||
"x": 0.1873,
|
||||
"y": 7.6654,
|
||||
"z": -3.9561,
|
||||
"pitch": 28.4994,
|
||||
"yaw": 0.5013,
|
||||
"roll": -0.9013
|
||||
},
|
||||
"torso": {
|
||||
"x": 0.1873,
|
||||
"y": 7.6654,
|
||||
"z": -3.9561,
|
||||
"pitch": 43.8857,
|
||||
"yaw": -0.2748,
|
||||
"roll": -0.5704,
|
||||
"bend": 32.4225,
|
||||
"axis": 178.4861
|
||||
},
|
||||
"rightArm": {
|
||||
"x": -4.8056,
|
||||
"y": 9.1559,
|
||||
"z": -2.5937,
|
||||
"pitch": 41.2104,
|
||||
"yaw": 11.1599,
|
||||
"roll": 11.5835,
|
||||
"bend": -28.9106,
|
||||
"axis": -6.4245
|
||||
},
|
||||
"leftArm": {
|
||||
"x": 5.1932,
|
||||
"y": 9.0568,
|
||||
"z": -2.5457,
|
||||
"pitch": 41.2134,
|
||||
"yaw": -10.0744,
|
||||
"roll": -11.3678,
|
||||
"bend": -34.2847,
|
||||
"axis": 3.7104
|
||||
},
|
||||
"rightLeg": {
|
||||
"x": -1.6738,
|
||||
"y": 16.311,
|
||||
"z": 4.4622,
|
||||
"pitch": -81.0086,
|
||||
"yaw": 25.937,
|
||||
"roll": 11.6147,
|
||||
"bend": -106.5341,
|
||||
"axis": 178.6748
|
||||
},
|
||||
"leftLeg": {
|
||||
"x": 2.1262,
|
||||
"y": 16.311,
|
||||
"z": 4.4622,
|
||||
"pitch": -80.1541,
|
||||
"yaw": -23.1741,
|
||||
"roll": -15.9396,
|
||||
"bend": -109.5512,
|
||||
"axis": 186.6826
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
{
|
||||
"version": 3,
|
||||
"name": "smooth wave",
|
||||
"description": "my first animation",
|
||||
"author": "operatordoge739",
|
||||
"emote": {
|
||||
"beginTick": 0,
|
||||
"endTick": 80,
|
||||
"stopTick": 81,
|
||||
"isLoop": false,
|
||||
"returnTick": 1,
|
||||
"easeBeforeKeyframe": false,
|
||||
"degrees": false,
|
||||
"moves": [
|
||||
{
|
||||
"tick": 8,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"body": {
|
||||
"pitch": -0.040079,
|
||||
"yaw": -0.062839,
|
||||
"roll": 0.013404
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 16,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.079561,
|
||||
"yaw": 0.00633,
|
||||
"roll": 0.104675
|
||||
},
|
||||
"body": {
|
||||
"pitch": 0.015338,
|
||||
"yaw": -0.101444,
|
||||
"roll": 0.071169
|
||||
},
|
||||
"rightArm": {
|
||||
"x": -5.642361,
|
||||
"y": 3.445788,
|
||||
"z": -0.219398,
|
||||
"pitch": -2.739643,
|
||||
"yaw": 1.23244,
|
||||
"roll": 0.274965
|
||||
},
|
||||
"leftArm": {
|
||||
"x": 5.348845,
|
||||
"y": 2.06144,
|
||||
"z": 0.456518,
|
||||
"pitch": -0.071698,
|
||||
"yaw": -0.032049,
|
||||
"roll": -0.245309
|
||||
},
|
||||
"rightLeg": {
|
||||
"x": -2.024828,
|
||||
"y": 12.367789,
|
||||
"z": 0.062098,
|
||||
"pitch": 0.005119,
|
||||
"yaw": 0.188187,
|
||||
"roll": 0.013892
|
||||
},
|
||||
"leftLeg": {
|
||||
"pitch": -0.015376,
|
||||
"yaw": -0.018557,
|
||||
"roll": -0.147605
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 23,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"body": {
|
||||
"pitch": 0.015075,
|
||||
"yaw": -0.096037,
|
||||
"roll": 0.011298
|
||||
},
|
||||
"rightArm": {
|
||||
"x": -5.569975,
|
||||
"y": 3.783614,
|
||||
"z": -0.392365,
|
||||
"pitch": -2.998378,
|
||||
"yaw": 0.910442,
|
||||
"roll": -0.644675
|
||||
},
|
||||
"rightLeg": {
|
||||
"x": -2.022849,
|
||||
"y": 12.008524,
|
||||
"z": 0.109452,
|
||||
"pitch": 0.007948,
|
||||
"yaw": 0.194292,
|
||||
"roll": 0.061405
|
||||
},
|
||||
"leftLeg": {
|
||||
"x": 1.828612,
|
||||
"y": 11.783937,
|
||||
"z": -0.067588
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 25,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.03656,
|
||||
"yaw": -0.056306,
|
||||
"roll": -0.010938
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 26,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"leftArm": {
|
||||
"x": 5.232569,
|
||||
"y": 2.037472,
|
||||
"z": 0.40348,
|
||||
"pitch": -0.120718,
|
||||
"yaw": -0.007399,
|
||||
"roll": -0.1239
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 30,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"body": {
|
||||
"pitch": 0.019292,
|
||||
"yaw": -0.103782,
|
||||
"roll": 0.055881
|
||||
},
|
||||
"rightArm": {
|
||||
"pitch": -2.902291,
|
||||
"yaw": 1.150272,
|
||||
"roll": -0.061632
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 31,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"rightLeg": {
|
||||
"x": -1.991897,
|
||||
"y": 12.157447,
|
||||
"z": 0.059089,
|
||||
"pitch": 0.005119,
|
||||
"yaw": 0.188187,
|
||||
"roll": 0.013892
|
||||
},
|
||||
"leftLeg": {
|
||||
"pitch": -0.015376,
|
||||
"yaw": -0.018557,
|
||||
"roll": -0.147605
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 32,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.075163,
|
||||
"yaw": -0.055531,
|
||||
"roll": -0.039248
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 36,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"leftArm": {
|
||||
"x": 5.232738,
|
||||
"y": 2.536962,
|
||||
"z": 0.337457,
|
||||
"pitch": -0.123332,
|
||||
"yaw": -0.017768,
|
||||
"roll": -0.198156
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 37,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"rightArm": {
|
||||
"pitch": -2.802795,
|
||||
"yaw": 1.020531,
|
||||
"roll": -0.376307
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 39,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"body": {
|
||||
"pitch": 0.01081,
|
||||
"yaw": -0.089193,
|
||||
"roll": 0.004038
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 41,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"head": {
|
||||
"pitch": 0.10722,
|
||||
"yaw": -0.023033,
|
||||
"roll": 0.072739
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 44,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"leftLeg": {
|
||||
"x": 2.093371,
|
||||
"y": 12.180333,
|
||||
"z": -0.403448,
|
||||
"pitch": 0.017084,
|
||||
"yaw": -0.010917,
|
||||
"roll": -0.023916
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 46,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"leftArm": {
|
||||
"x": 5.094209,
|
||||
"y": 2.963737,
|
||||
"z": 0.290066,
|
||||
"pitch": -0.130177,
|
||||
"yaw": 0.00407,
|
||||
"roll": -0.014502
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 49,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"body": {
|
||||
"pitch": -0.035429,
|
||||
"yaw": -0.084483,
|
||||
"roll": 0.015859
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 56,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"head": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 57,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"body": {
|
||||
"pitch": 0.015473,
|
||||
"yaw": -0.095783,
|
||||
"roll": 0.000252
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 59,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"leftArm": {
|
||||
"x": 5.211452,
|
||||
"y": 2.287216,
|
||||
"z": 0.189115,
|
||||
"pitch": 0.007903,
|
||||
"yaw": -0.010692,
|
||||
"roll": -0.024953
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 60,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"rightArm": {
|
||||
"x": -5.0,
|
||||
"y": 2.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.093257,
|
||||
"yaw": 0.058088,
|
||||
"roll": 0.082813
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 68,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"rightArm": {
|
||||
"x": -5.0,
|
||||
"y": 2.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
},
|
||||
"rightLeg": {
|
||||
"x": -1.9,
|
||||
"y": 12.0,
|
||||
"z": 0.1,
|
||||
"pitch": 0.000513,
|
||||
"yaw": 0.10914,
|
||||
"roll": -0.004835
|
||||
},
|
||||
"leftLeg": {
|
||||
"x": 1.9,
|
||||
"y": 12.0,
|
||||
"z": -0.1,
|
||||
"pitch": 0.000177,
|
||||
"yaw": -0.167171,
|
||||
"roll": -0.002612
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 72,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"leftArm": {
|
||||
"x": 5.0,
|
||||
"y": 2.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.053822,
|
||||
"yaw": -0.009281,
|
||||
"roll": -0.012456
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 77,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"leftLeg": {
|
||||
"x": 1.9,
|
||||
"y": 12.0,
|
||||
"z": -0.1,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 78,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"leftArm": {
|
||||
"x": 5.0,
|
||||
"y": 2.0,
|
||||
"z": 0.0,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"tick": 80,
|
||||
"easing": "EASEINOUTQUAD",
|
||||
"rightLeg": {
|
||||
"x": -1.9,
|
||||
"y": 12.0,
|
||||
"z": 0.1,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"roll": 0.0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2"/></svg>
|
||||
|
After Width: | Height: | Size: 317 B |
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
|
||||
|
After Width: | Height: | Size: 210 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
|
||||
|
After Width: | Height: | Size: 207 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 322 B |
|
Before Width: | Height: | Size: 1.6 KiB |