mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 19:58:00 +00:00
517 lines
21 KiB
Java
517 lines
21 KiB
Java
package forge.assets;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
import com.badlogic.gdx.Gdx;
|
|
import com.badlogic.gdx.files.FileHandle;
|
|
import com.badlogic.gdx.graphics.Color;
|
|
import com.badlogic.gdx.graphics.Pixmap;
|
|
import com.badlogic.gdx.graphics.Texture;
|
|
import com.badlogic.gdx.graphics.g2d.TextureRegion;
|
|
import com.badlogic.gdx.utils.Array;
|
|
|
|
import forge.Forge;
|
|
import forge.assets.FSkinImage.SourceFile;
|
|
import forge.card.CardFaceSymbols;
|
|
import forge.gui.FThreads;
|
|
import forge.gui.GuiBase;
|
|
import forge.localinstance.properties.ForgeConstants;
|
|
import forge.localinstance.properties.ForgePreferences;
|
|
import forge.localinstance.properties.ForgePreferences.FPref;
|
|
import forge.localinstance.skin.FSkinProp;
|
|
import forge.model.FModel;
|
|
import forge.screens.LoadingOverlay;
|
|
import forge.screens.SplashScreen;
|
|
import forge.toolbox.FProgressBar;
|
|
import forge.util.WordUtil;
|
|
|
|
public class FSkin {
|
|
private static final Map<FSkinProp, FSkinImage> images = new HashMap<>(512);
|
|
private static final Map<Integer, TextureRegion> avatars = new HashMap<>(150);
|
|
private static final Map<Integer, TextureRegion> sleeves = new HashMap<>(64);
|
|
private static final Map<Integer, TextureRegion> borders = new HashMap<>();
|
|
private static final Map<Integer, TextureRegion> deckbox = new HashMap<>();
|
|
|
|
private static Array<String> allSkins;
|
|
private static FileHandle preferredDir;
|
|
private static String preferredName;
|
|
private static boolean loaded = false;
|
|
public static Texture hdLogo = null;
|
|
public static Texture overlay_alpha = null;
|
|
|
|
public static void changeSkin(final String skinName) {
|
|
final ForgePreferences prefs = FModel.getPreferences();
|
|
if (skinName.equals(prefs.getPref(FPref.UI_SKIN))) { return; }
|
|
|
|
//save skin preference
|
|
prefs.setPref(FPref.UI_SKIN, skinName);
|
|
prefs.save();
|
|
|
|
//load skin
|
|
loaded = false; //reset this temporarily until end of loadFull()
|
|
|
|
final LoadingOverlay loader = new LoadingOverlay("Loading new theme...");
|
|
loader.show(); //show loading overlay then delay running remaining logic so UI can respond
|
|
FThreads.invokeInBackgroundThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
FThreads.invokeInEdtLater(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
loadLight(skinName, null);
|
|
loadFull(null);
|
|
loader.setCaption("Loading fonts...");
|
|
FThreads.invokeInBackgroundThread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
FSkinFont.deleteCachedFiles(); //delete cached font files so font can be update for new skin
|
|
FSkinFont.updateAll();
|
|
//CardImageRenderer.forceStaticFieldUpdate();
|
|
FThreads.invokeInEdtLater(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
loader.hide();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
/*
|
|
* Loads a "light" version of FSkin, just enough for the splash screen:
|
|
* skin name. Generates custom skin settings, fonts, and backgrounds.
|
|
*
|
|
*
|
|
* @param skinName
|
|
* the skin name
|
|
*/
|
|
public static void loadLight(String skinName, final SplashScreen splashScreen) {
|
|
preferredName = skinName.toLowerCase().replace(' ', '_');
|
|
|
|
//reset hd buttons/icons
|
|
Forge.hdbuttons = false;
|
|
Forge.hdstart = false;
|
|
|
|
//ensure skins directory exists
|
|
final FileHandle dir = Gdx.files.absolute(ForgeConstants.CACHE_SKINS_DIR);
|
|
if (!dir.exists() || !dir.isDirectory()) {
|
|
//if skins directory doesn't exist, point to internal assets/skin directory instead for the sake of the splash screen
|
|
preferredDir = Gdx.files.internal("fallback_skin");
|
|
}
|
|
else {
|
|
if (splashScreen != null) {
|
|
if (allSkins == null) { //initialize
|
|
allSkins = new Array<>();
|
|
allSkins.add("Default"); //init default
|
|
final Array<String> skinDirectoryNames = getSkinDirectoryNames();
|
|
for (final String skinDirectoryName : skinDirectoryNames) {
|
|
allSkins.add(WordUtil.capitalize(skinDirectoryName.replace('_', ' ')));
|
|
}
|
|
allSkins.sort();
|
|
}
|
|
}
|
|
|
|
// Non-default (preferred) skin name and dir.
|
|
preferredDir = Gdx.files.absolute(preferredName.equals("default") ? ForgeConstants.BASE_SKINS_DIR + preferredName : ForgeConstants.CACHE_SKINS_DIR + preferredName);
|
|
if (!preferredDir.exists() || !preferredDir.isDirectory()) {
|
|
preferredDir.mkdirs();
|
|
}
|
|
}
|
|
|
|
FSkinTexture.BG_TEXTURE.load(); //load background texture early for splash screen
|
|
|
|
//load theme logo while changing skins
|
|
final FileHandle theme_logo = getSkinFile("hd_logo.png");
|
|
if (theme_logo.exists()) {
|
|
Texture txOverlay = new Texture(theme_logo, true);
|
|
txOverlay.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
|
|
hdLogo = txOverlay;
|
|
} else {
|
|
hdLogo = null;
|
|
}
|
|
final FileHandle duals_overlay = getDefaultSkinFile("overlay_alpha.png");
|
|
if (duals_overlay.exists()) {
|
|
Texture txAlphaLines = new Texture(duals_overlay, true);
|
|
txAlphaLines.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
|
|
overlay_alpha = txAlphaLines;
|
|
} else {
|
|
overlay_alpha = null;
|
|
}
|
|
|
|
if (splashScreen != null) {
|
|
final FileHandle f = getSkinFile("bg_splash.png");
|
|
final FileHandle f2 = getSkinFile("bg_splash_hd.png"); //HD Splashscreen
|
|
|
|
if (!f.exists()) {
|
|
if (!skinName.equals("default")) {
|
|
FSkin.loadLight("default", splashScreen);
|
|
}
|
|
return;
|
|
}
|
|
|
|
try {
|
|
Texture txSplash = new Texture(f);
|
|
final int w = txSplash.getWidth();
|
|
final int h = txSplash.getHeight();
|
|
|
|
if (f2.exists()) {
|
|
Texture txSplashHD = new Texture(f2, true);
|
|
txSplashHD.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
|
|
splashScreen.setBackground(new TextureRegion(txSplashHD));
|
|
} else {
|
|
splashScreen.setBackground(new TextureRegion(txSplash, 0, 0, w, h - 100));
|
|
}
|
|
|
|
Pixmap pxSplash = new Pixmap(f);
|
|
FProgressBar.BACK_COLOR = new Color(pxSplash.getPixel(25, h - 75));
|
|
FProgressBar.FORE_COLOR = new Color(pxSplash.getPixel(75, h - 75));
|
|
FProgressBar.SEL_BACK_COLOR = new Color(pxSplash.getPixel(25, h - 25));
|
|
FProgressBar.SEL_FORE_COLOR = new Color(pxSplash.getPixel(75, h - 25));
|
|
}
|
|
catch (final Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
loaded = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loads two sprites: the default (which should be a complete
|
|
* collection of all symbols) and the preferred (which may be
|
|
* incomplete).
|
|
*
|
|
* Font must be present in the skin folder, and will not
|
|
* be replaced by default. The fonts are pre-derived
|
|
* in this method and saved in a HashMap for future access.
|
|
*
|
|
* Color swatches must be present in the preferred
|
|
* sprite, and will not be replaced by default.
|
|
*
|
|
* Background images must be present in skin folder,
|
|
* and will not be replaced by default.
|
|
*
|
|
* Icons, however, will be pulled from the two sprites. Obviously,
|
|
* preferred takes precedence over default, but if something is
|
|
* missing, the default picture is retrieved.
|
|
*/
|
|
public static void loadFull(final SplashScreen splashScreen) {
|
|
if (splashScreen != null) {
|
|
// Preferred skin name must be called via loadLight() method,
|
|
// which does some cleanup and init work.
|
|
if (FSkin.preferredName.isEmpty()) { FSkin.loadLight("default", splashScreen); }
|
|
}
|
|
|
|
avatars.clear();
|
|
sleeves.clear();
|
|
|
|
boolean textureFilter = Forge.isTextureFilteringEnabled();
|
|
|
|
final Map<String, Texture> textures = new HashMap<>();
|
|
|
|
// Grab and test various sprite files.
|
|
final FileHandle f1 = getDefaultSkinFile(SourceFile.ICONS.getFilename());
|
|
final FileHandle f2 = getSkinFile(SourceFile.ICONS.getFilename());
|
|
final FileHandle f3 = getDefaultSkinFile(SourceFile.FOILS.getFilename());
|
|
final FileHandle f4 = getDefaultSkinFile(ForgeConstants.SPRITE_AVATARS_FILE);
|
|
final FileHandle f5 = getSkinFile(ForgeConstants.SPRITE_AVATARS_FILE);
|
|
final FileHandle f6 = getDefaultSkinFile(SourceFile.OLD_FOILS.getFilename());
|
|
final FileHandle f7 = getSkinFile(ForgeConstants.SPRITE_MANAICONS_FILE);
|
|
final FileHandle f8 = getDefaultSkinFile(ForgeConstants.SPRITE_SLEEVES_FILE);
|
|
final FileHandle f9 = getDefaultSkinFile(ForgeConstants.SPRITE_SLEEVES2_FILE);
|
|
final FileHandle f10 = getDefaultSkinFile(ForgeConstants.SPRITE_BORDER_FILE);
|
|
final FileHandle f11 = getSkinFile(ForgeConstants.SPRITE_BUTTONS_FILE);
|
|
final FileHandle f12 = getSkinFile(ForgeConstants.SPRITE_START_FILE);
|
|
final FileHandle f13 = getDefaultSkinFile(ForgeConstants.SPRITE_DECKBOX_FILE);
|
|
|
|
/*TODO Themeable
|
|
final FileHandle f14 = getDefaultSkinFile(ForgeConstants.SPRITE_SETLOGO_FILE);
|
|
final FileHandle f15 = getSkinFile(ForgeConstants.SPRITE_SETLOGO_FILE);
|
|
final FileHandle f16 = getDefaultSkinFile(ForgeConstants.SPRITE_WATERMARK_FILE);
|
|
*/
|
|
|
|
try {
|
|
textures.put(f1.path(), new Texture(f1));
|
|
Pixmap preferredIcons = new Pixmap(f1);
|
|
if (f2.exists()) {
|
|
textures.put(f2.path(), new Texture(f2));
|
|
preferredIcons = new Pixmap(f2);
|
|
}
|
|
|
|
textures.put(f3.path(), new Texture(f3));
|
|
if (f6.exists()) {
|
|
textures.put(f6.path(), new Texture(f6));
|
|
}
|
|
else {
|
|
textures.put(f6.path(), textures.get(f3.path()));
|
|
}
|
|
if (f7.exists()){
|
|
Texture t = new Texture(f7, true);
|
|
//t.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
|
|
textures.put(f7.path(), t);
|
|
}
|
|
|
|
//hdbuttons
|
|
if (f11.exists()) {
|
|
if (GuiBase.isAndroid() && Forge.totalDeviceRAM <5000) {
|
|
Forge.hdbuttons = false;
|
|
} else {
|
|
Texture t = new Texture(f11, true);
|
|
t.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
|
|
textures.put(f11.path(), t);
|
|
Forge.hdbuttons = true;
|
|
}
|
|
} else { Forge.hdbuttons = false; } //how to refresh buttons when a theme don't have hd buttons?
|
|
if (f12.exists()) {
|
|
if (GuiBase.isAndroid() && Forge.totalDeviceRAM <5000) {
|
|
Forge.hdstart = false;
|
|
} else {
|
|
Texture t = new Texture(f12, true);
|
|
t.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
|
|
textures.put(f12.path(), t);
|
|
Forge.hdstart = true;
|
|
}
|
|
} else { Forge.hdstart = false; }
|
|
//update colors
|
|
for (final FSkinColor.Colors c : FSkinColor.Colors.values()) {
|
|
c.setColor(new Color(preferredIcons.getPixel(c.getX(), c.getY())));
|
|
}
|
|
|
|
//load images
|
|
for (FSkinImage image : FSkinImage.values()) {
|
|
if (GuiBase.isAndroid()) {
|
|
if (Forge.totalDeviceRAM>5000)
|
|
image.load(textures, preferredIcons);
|
|
else if (image.toString().equals("HDMULTI"))
|
|
image.load(textures, preferredIcons);
|
|
else if (!image.toString().startsWith("HD"))
|
|
image.load(textures, preferredIcons);
|
|
} else {
|
|
image.load(textures, preferredIcons);
|
|
}
|
|
}
|
|
for (FSkinTexture texture : FSkinTexture.values()) {
|
|
if (texture != FSkinTexture.BG_TEXTURE) {
|
|
texture.load();
|
|
}
|
|
}
|
|
|
|
//assemble avatar textures
|
|
int counter = 0;
|
|
int scount = 0;
|
|
Color pxTest;
|
|
Pixmap pxDefaultAvatars, pxPreferredAvatars, pxDefaultSleeves;
|
|
Texture txDefaultAvatars, txPreferredAvatars, txDefaultSleeves;
|
|
|
|
pxDefaultAvatars = new Pixmap(f4);
|
|
pxDefaultSleeves = new Pixmap(f8);
|
|
txDefaultAvatars = new Texture(f4, textureFilter);
|
|
if (textureFilter)
|
|
txDefaultAvatars.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
|
|
txDefaultSleeves = new Texture(f8, textureFilter);
|
|
if (textureFilter)
|
|
txDefaultSleeves.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
|
|
|
|
if (f5.exists()) {
|
|
pxPreferredAvatars = new Pixmap(f5);
|
|
txPreferredAvatars = new Texture(f5, textureFilter);
|
|
if (textureFilter)
|
|
txPreferredAvatars.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
|
|
|
|
final int pw = pxPreferredAvatars.getWidth();
|
|
final int ph = pxPreferredAvatars.getHeight();
|
|
|
|
for (int j = 0; j < ph; j += 100) {
|
|
for (int i = 0; i < pw; i += 100) {
|
|
if (i == 0 && j == 0) { continue; }
|
|
pxTest = new Color(pxPreferredAvatars.getPixel(i + 50, j + 50));
|
|
if (pxTest.a == 0) { continue; }
|
|
FSkin.avatars.put(counter++, new TextureRegion(txPreferredAvatars, i, j, 100, 100));
|
|
}
|
|
}
|
|
pxPreferredAvatars.dispose();
|
|
} else if (!FSkin.preferredName.isEmpty()){
|
|
//workaround bug crash fix if missing sprite avatar on preferred theme for quest tournament...
|
|
//i really don't know why it needs to populate the avatars twice.... needs investigation
|
|
final int pw = pxDefaultAvatars.getWidth();
|
|
final int ph = pxDefaultAvatars.getHeight();
|
|
|
|
for (int j = 0; j < ph; j += 100) {
|
|
for (int i = 0; i < pw; i += 100) {
|
|
if (i == 0 && j == 0) { continue; }
|
|
pxTest = new Color(pxDefaultAvatars.getPixel(i + 50, j + 50));
|
|
if (pxTest.a == 0) { continue; }
|
|
FSkin.avatars.put(counter++, new TextureRegion(txDefaultAvatars, i, j, 100, 100));
|
|
}
|
|
}
|
|
}
|
|
|
|
final int aw = pxDefaultAvatars.getWidth();
|
|
final int ah = pxDefaultAvatars.getHeight();
|
|
|
|
for (int j = 0; j < ah; j += 100) {
|
|
for (int i = 0; i < aw; i += 100) {
|
|
if (i == 0 && j == 0) { continue; }
|
|
pxTest = new Color(pxDefaultAvatars.getPixel(i + 50, j + 50));
|
|
if (pxTest.a == 0) { continue; }
|
|
FSkin.avatars.put(counter++, new TextureRegion(txDefaultAvatars, i, j, 100, 100));
|
|
}
|
|
}
|
|
|
|
|
|
final int sw = pxDefaultSleeves.getWidth();
|
|
final int sh = pxDefaultSleeves.getHeight();
|
|
|
|
for (int j = 0; j < sh; j += 500) {
|
|
for (int i = 0; i < sw; i += 360) {
|
|
pxTest = new Color(pxDefaultSleeves.getPixel(i + 180, j + 250));
|
|
if (pxTest.a == 0) { continue; }
|
|
FSkin.sleeves.put(scount++, new TextureRegion(txDefaultSleeves, i, j, 360, 500));
|
|
}
|
|
}
|
|
|
|
//re init second set of sleeves
|
|
pxDefaultSleeves = new Pixmap(f9);
|
|
txDefaultSleeves = new Texture(f9, textureFilter);
|
|
if (textureFilter)
|
|
txDefaultSleeves.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
|
|
|
|
final int sw2 = pxDefaultSleeves.getWidth();
|
|
final int sh2 = pxDefaultSleeves.getHeight();
|
|
|
|
for (int j = 0; j < sh2; j += 500) {
|
|
for (int i = 0; i < sw2; i += 360) {
|
|
pxTest = new Color(pxDefaultSleeves.getPixel(i + 180, j + 250));
|
|
if (pxTest.a == 0) { continue; }
|
|
FSkin.sleeves.put(scount++, new TextureRegion(txDefaultSleeves, i, j, 360, 500));
|
|
}
|
|
}
|
|
//borders
|
|
Texture bordersBW = new Texture(f10);
|
|
FSkin.borders.put(0, new TextureRegion(bordersBW, 2, 2, 672, 936));
|
|
FSkin.borders.put(1, new TextureRegion(bordersBW, 676, 2, 672, 936));
|
|
//deckboxes
|
|
Texture deckboxes = new Texture(f13, textureFilter);
|
|
if (textureFilter)
|
|
deckboxes.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear);
|
|
//gold bg
|
|
FSkin.deckbox.put(0, new TextureRegion(deckboxes, 2, 2, 488, 680));
|
|
//deck box for card art
|
|
FSkin.deckbox.put(1, new TextureRegion(deckboxes, 492, 2, 488, 680));
|
|
//generic deck box
|
|
FSkin.deckbox.put(2, new TextureRegion(deckboxes, 982, 2, 488, 680));
|
|
|
|
preferredIcons.dispose();
|
|
pxDefaultAvatars.dispose();
|
|
pxDefaultSleeves.dispose();
|
|
}
|
|
catch (final Exception e) {
|
|
System.err.println("FSkin$loadFull: Missing a sprite (default icons, "
|
|
+ "preferred icons, or foils.");
|
|
e.printStackTrace();
|
|
}
|
|
|
|
// Run through enums and load their coords.
|
|
FSkinColor.updateAll();
|
|
|
|
// Images loaded; can start UI init.
|
|
loaded = true;
|
|
|
|
if (splashScreen != null) {
|
|
CardFaceSymbols.loadImages();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the name.
|
|
*
|
|
* @return Name of the current skin.
|
|
*/
|
|
public static String getName() {
|
|
return FSkin.preferredName;
|
|
}
|
|
|
|
/**
|
|
* Gets a FileHandle for a file within the directory where skin files should be stored
|
|
*/
|
|
public static FileHandle getSkinFile(String filename) {
|
|
return preferredDir.child(filename);
|
|
}
|
|
|
|
/**
|
|
* Gets a FileHandle for a file within the directory where the default skin files should be stored
|
|
*/
|
|
public static FileHandle getDefaultSkinFile(String filename) {
|
|
return Gdx.files.absolute(ForgeConstants.DEFAULT_SKINS_DIR + filename);
|
|
}
|
|
|
|
/**
|
|
* Gets a FileHandle for a file within the planechase cache directory
|
|
*/
|
|
public static FileHandle getCachePlanechaseFile(String filename) {
|
|
return Gdx.files.absolute(ForgeConstants.CACHE_PLANECHASE_PICS_DIR + filename);
|
|
}
|
|
|
|
public static FileHandle getSkinDir() {
|
|
return preferredDir;
|
|
}
|
|
|
|
/**
|
|
* Gets the skins.
|
|
*
|
|
* @return the skins
|
|
*/
|
|
public static Array<String> getSkinDirectoryNames() {
|
|
final Array<String> mySkins = new Array<>();
|
|
|
|
final FileHandle dir = Gdx.files.absolute(ForgeConstants.CACHE_SKINS_DIR);
|
|
for (FileHandle skinFile : dir.list()) {
|
|
String skinName = skinFile.name();
|
|
if (skinName.equalsIgnoreCase(".svn")) { continue; }
|
|
if (skinName.equalsIgnoreCase(".DS_Store")) { continue; }
|
|
mySkins.add(skinName);
|
|
}
|
|
|
|
return mySkins;
|
|
}
|
|
|
|
public static Iterable<String> getAllSkins() {
|
|
if (allSkins != null) {
|
|
allSkins.clear();
|
|
allSkins.add("Default"); //init default
|
|
final Array<String> skinDirectoryNames = getSkinDirectoryNames();
|
|
for (final String skinDirectoryName : skinDirectoryNames) {
|
|
allSkins.add(WordUtil.capitalize(skinDirectoryName.replace('_', ' ')));
|
|
}
|
|
allSkins.sort();
|
|
}
|
|
return allSkins;
|
|
}
|
|
|
|
public static Map<FSkinProp, FSkinImage> getImages() {
|
|
return images;
|
|
}
|
|
|
|
public static Map<Integer, TextureRegion> getAvatars() {
|
|
return avatars;
|
|
}
|
|
|
|
public static Map<Integer, TextureRegion> getSleeves() {
|
|
return sleeves;
|
|
}
|
|
|
|
public static Map<Integer, TextureRegion> getBorders() {
|
|
return borders;
|
|
}
|
|
|
|
public static Map<Integer, TextureRegion> getDeckbox() {
|
|
return deckbox;
|
|
}
|
|
|
|
public static boolean isLoaded() { return loaded; }
|
|
}
|