From 8d75855191bf8e193e7f15f6586f554532737e66 Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Wed, 23 Dec 2020 16:27:34 -0800 Subject: [PATCH] [libgdx] Refactored SkeletonViewer into multiple files. Load PMA setting from new atlas format. --- .../spine/SkeletonViewAtlas.java | 107 +++ .../spine/SkeletonViewer.java | 835 +----------------- .../spine/SkeletonViewerUI.java | 723 +++++++++++++++ 3 files changed, 860 insertions(+), 805 deletions(-) create mode 100644 spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewAtlas.java create mode 100644 spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewerUI.java diff --git a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewAtlas.java b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewAtlas.java new file mode 100644 index 000000000..6b21e94cd --- /dev/null +++ b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewAtlas.java @@ -0,0 +1,107 @@ + +package com.esotericsoftware.spine; + +import static com.esotericsoftware.spine.SkeletonViewer.*; + +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.Pixmap.Format; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.Texture.TextureFilter; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import com.badlogic.gdx.graphics.g2d.TextureAtlas.TextureAtlasData.Page; +import com.badlogic.gdx.utils.Null; + +class SkeletonViewAtlas extends TextureAtlas { + private final SkeletonViewer viewer; + private @Null FileHandle atlasFile; + private final AtlasRegion fake; + + public SkeletonViewAtlas (final SkeletonViewer viewer, @Null FileHandle skeletonFile) { + this.viewer = viewer; + + atlasFile = findAtlasFile(skeletonFile); + if (atlasFile != null) { + final TextureAtlasData atlasData = new TextureAtlasData(atlasFile, atlasFile.parent(), false); + Gdx.app.postRunnable(new Runnable() { + public void run () { + boolean linear = true, pma = false; + for (int i = 0, n = atlasData.getPages().size; i < n; i++) { + Page page = atlasData.getPages().get(i); + if (page.pma) pma = true; + if (page.minFilter != TextureFilter.Linear || page.magFilter != TextureFilter.Linear) { + linear = false; + break; + } + } + viewer.ui.linearCheckbox.setChecked(linear); + viewer.ui.pmaCheckbox.setChecked(pma); + } + }); + try { + load(atlasData); + } catch (Throwable ex) { + System.out.println("Error loading atlas: " + atlasFile.file().getAbsolutePath()); + ex.printStackTrace(); + viewer.ui.toast("Error loading atlas: " + atlasFile.name()); + atlasFile = null; + } + } + + // Setup a texture atlas that uses a white image for images not found in the atlas. + Pixmap pixmap = new Pixmap(32, 32, Format.RGBA8888); + pixmap.setColor(new Color(1, 1, 1, 0.33f)); + pixmap.fill(); + fake = new AtlasRegion(new Texture(pixmap), 0, 0, 32, 32); + pixmap.dispose(); + } + + private @Null FileHandle findAtlasFile (FileHandle skeletonFile) { + String baseName = skeletonFile.name(); + for (String startSuffix : startSuffixes) { + for (String endSuffix : endSuffixes) { + for (String dataSuffix : dataSuffixes) { + String suffix = startSuffix + dataSuffix + endSuffix; + if (baseName.endsWith(suffix)) { + FileHandle file = findAtlasFile(skeletonFile, baseName.substring(0, baseName.length() - suffix.length())); + if (file != null) return file; + } + } + } + } + return findAtlasFile(skeletonFile, baseName); + } + + private @Null FileHandle findAtlasFile (FileHandle skeletonFile, String baseName) { + for (String startSuffix : startSuffixes) { + for (String endSuffix : endSuffixes) { + for (String suffix : atlasSuffixes) { + FileHandle file = skeletonFile.sibling(baseName + startSuffix + suffix + endSuffix); + if (file.exists()) return file; + } + } + } + return null; + } + + public AtlasRegion findRegion (String name) { + AtlasRegion region = super.findRegion(name); + if (region == null) { + // Look for separate image file. + FileHandle file = viewer.skeletonFile.sibling(name + ".png"); + if (file.exists()) { + Texture texture = new Texture(file); + texture.setFilter(TextureFilter.Linear, TextureFilter.Linear); + region = new AtlasRegion(texture, 0, 0, texture.getWidth(), texture.getHeight()); + region.name = name; + } + } + return region != null ? region : fake; + } + + public long lastModified () { + return atlasFile == null ? 0 : atlasFile.lastModified(); + } +} diff --git a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java index cd8145b74..6e020fee1 100644 --- a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java +++ b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java @@ -29,17 +29,11 @@ package com.esotericsoftware.spine; -import static com.badlogic.gdx.math.Interpolation.*; -import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*; - -import java.io.File; import java.lang.Thread.UncaughtExceptionHandler; import java.lang.reflect.Field; import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.InputAdapter; -import com.badlogic.gdx.InputMultiplexer; import com.badlogic.gdx.Preferences; import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; @@ -47,36 +41,8 @@ import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.OrthographicCamera; -import com.badlogic.gdx.graphics.Pixmap; -import com.badlogic.gdx.graphics.Pixmap.Format; -import com.badlogic.gdx.graphics.Texture; -import com.badlogic.gdx.graphics.Texture.TextureFilter; -import com.badlogic.gdx.graphics.g2d.TextureAtlas; -import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion; -import com.badlogic.gdx.graphics.g2d.TextureAtlas.TextureAtlasData; -import com.badlogic.gdx.graphics.g2d.TextureAtlas.TextureAtlasData.Page; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType; -import com.badlogic.gdx.math.MathUtils; -import com.badlogic.gdx.scenes.scene2d.Actor; -import com.badlogic.gdx.scenes.scene2d.InputEvent; -import com.badlogic.gdx.scenes.scene2d.InputListener; -import com.badlogic.gdx.scenes.scene2d.Stage; -import com.badlogic.gdx.scenes.scene2d.Touchable; -import com.badlogic.gdx.scenes.scene2d.ui.ButtonGroup; -import com.badlogic.gdx.scenes.scene2d.ui.CheckBox; -import com.badlogic.gdx.scenes.scene2d.ui.Image; -import com.badlogic.gdx.scenes.scene2d.ui.Label; -import com.badlogic.gdx.scenes.scene2d.ui.List; -import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane; -import com.badlogic.gdx.scenes.scene2d.ui.Slider; -import com.badlogic.gdx.scenes.scene2d.ui.Table; -import com.badlogic.gdx.scenes.scene2d.ui.TextButton; -import com.badlogic.gdx.scenes.scene2d.ui.WidgetGroup; -import com.badlogic.gdx.scenes.scene2d.ui.Window; -import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; -import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; -import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Null; import com.badlogic.gdx.utils.StringBuilder; @@ -87,60 +53,58 @@ import com.esotericsoftware.spine.AnimationState.AnimationStateAdapter; import com.esotericsoftware.spine.AnimationState.TrackEntry; import com.esotericsoftware.spine.utils.TwoColorPolygonBatch; -import java.awt.FileDialog; -import java.awt.Frame; import java.awt.Toolkit; public class SkeletonViewer extends ApplicationAdapter { + static final String version = ""; // Replaced by build. static final float checkModifiedInterval = 0.250f; static final float reloadDelay = 1; - static float uiScale = 1; - static String[] startSuffixes = {"", "-pro", "-ess"}; - static String[] dataSuffixes = {".json", ".skel"}; - static String[] endSuffixes = {"", ".txt", ".bytes"}; - static String[] atlasSuffixes = {".atlas", "-pma.atlas"}; + static final String[] startSuffixes = {"", "-pro", "-ess"}; + static final String[] dataSuffixes = {".json", ".skel"}; + static final String[] endSuffixes = {"", ".txt", ".bytes"}; + static final String[] atlasSuffixes = {".atlas", "-pma.atlas"}; static String[] args; - static final String version = ""; // Replaced by build. + static float uiScale = 1; - UI ui; - - OrthographicCamera camera; + Preferences prefs; TwoColorPolygonBatch batch; - TextureAtlas atlas; + OrthographicCamera camera; SkeletonRenderer renderer; SkeletonRendererDebug debugRenderer; + SkeletonViewerUI ui; + + SkeletonViewAtlas atlas; SkeletonData skeletonData; Skeleton skeleton; AnimationState state; FileHandle skeletonFile; long skeletonModified, atlasModified; float lastModifiedCheck, reloadTimer; - StringBuilder status = new StringBuilder(); - Preferences prefs; + final StringBuilder status = new StringBuilder(); public void create () { Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() { public void uncaughtException (Thread thread, Throwable ex) { + System.out.println("Uncaught exception:"); ex.printStackTrace(); Runtime.getRuntime().halt(0); // Prevent Swing from keeping JVM alive. } }); prefs = Gdx.app.getPreferences("spine-skeletonviewer"); - ui = new UI(); batch = new TwoColorPolygonBatch(3100); camera = new OrthographicCamera(); renderer = new SkeletonRenderer(); debugRenderer = new SkeletonRendererDebug(); + ui = new SkeletonViewerUI(this); resetCameraPosition(); ui.loadPrefs(); if (args.length == 0) { loadSkeleton( Gdx.files.internal(Gdx.app.getPreferences("spine-skeletonviewer").getString("lastFile", "spineboy/spineboy.json"))); - } else { + } else loadSkeleton(Gdx.files.internal(args[0])); - } ui.loadPrefs(); ui.prefsLoaded = true; @@ -152,90 +116,24 @@ public class SkeletonViewer extends ApplicationAdapter { } } - FileHandle atlasFile (FileHandle skeletonFile) { - String baseName = skeletonFile.name(); - for (String startSuffix : startSuffixes) { - for (String endSuffix : endSuffixes) { - for (String dataSuffix : dataSuffixes) { - String suffix = startSuffix + dataSuffix + endSuffix; - if (baseName.endsWith(suffix)) { - FileHandle file = atlasFile(skeletonFile, baseName.substring(0, baseName.length() - suffix.length())); - if (file != null) return file; - } - } - } - } - return atlasFile(skeletonFile, baseName); - } - - private FileHandle atlasFile (FileHandle skeletonFile, String baseName) { - for (String startSuffix : startSuffixes) { - for (String endSuffix : endSuffixes) { - for (String suffix : atlasSuffixes) { - FileHandle file = skeletonFile.sibling(baseName + startSuffix + suffix + endSuffix); - if (file.exists()) return file; - } - } - } - return null; - } - - void loadSkeleton (final FileHandle skeletonFile) { + void loadSkeleton (final @Null FileHandle skeletonFile) { if (skeletonFile == null) return; - FileHandle atlasFile = atlasFile(skeletonFile); try { - // Setup a texture atlas that uses a white image for images not found in the atlas. - Pixmap pixmap = new Pixmap(32, 32, Format.RGBA8888); - pixmap.setColor(new Color(1, 1, 1, 0.33f)); - pixmap.fill(); - final AtlasRegion fake = new AtlasRegion(new Texture(pixmap), 0, 0, 32, 32); - pixmap.dispose(); - - TextureAtlasData atlasData = null; - if (atlasFile != null) { - atlasData = new TextureAtlasData(atlasFile, atlasFile.parent(), false); - boolean linear = true; - for (int i = 0, n = atlasData.getPages().size; i < n; i++) { - Page page = atlasData.getPages().get(i); - if (page.minFilter != TextureFilter.Linear || page.magFilter != TextureFilter.Linear) { - linear = false; - break; - } - } - ui.linearCheckbox.setChecked(linear); - } - - atlas = new TextureAtlas(atlasData) { - public AtlasRegion findRegion (String name) { - AtlasRegion region = super.findRegion(name); - if (region == null) { - // Look for separate image file. - FileHandle file = skeletonFile.sibling(name + ".png"); - if (file.exists()) { - Texture texture = new Texture(file); - texture.setFilter(TextureFilter.Linear, TextureFilter.Linear); - region = new AtlasRegion(texture, 0, 0, texture.getWidth(), texture.getHeight()); - region.name = name; - } - } - return region != null ? region : fake; - } - }; + atlas = new SkeletonViewAtlas(this, skeletonFile); // Load skeleton data. String extension = skeletonFile.extension(); - if (extension.equalsIgnoreCase("json") || extension.equalsIgnoreCase("txt")) { - SkeletonJson json = new SkeletonJson(atlas); - json.setScale(ui.loadScaleSlider.getValue()); - skeletonData = json.readSkeletonData(skeletonFile); - } else { - SkeletonBinary binary = new SkeletonBinary(atlas); - binary.setScale(ui.loadScaleSlider.getValue()); - skeletonData = binary.readSkeletonData(skeletonFile); - if (skeletonData.getBones().size == 0) throw new Exception("No bones in skeleton data."); - } + SkeletonLoader loader; + if (extension.equalsIgnoreCase("json") || extension.equalsIgnoreCase("txt")) + loader = new SkeletonJson(atlas); + else + loader = new SkeletonBinary(atlas); + loader.setScale(ui.loadScaleSlider.getValue()); + skeletonData = loader.readSkeletonData(skeletonFile); + if (skeletonData.getBones().size == 0) throw new Exception("No bones in skeleton data."); } catch (Throwable ex) { + System.out.println("Error loading skeleton: " + skeletonFile.file().getAbsolutePath()); ex.printStackTrace(); ui.toast("Error loading skeleton: " + skeletonFile.name()); lastModifiedCheck = 5; @@ -258,7 +156,7 @@ public class SkeletonViewer extends ApplicationAdapter { this.skeletonFile = skeletonFile; skeletonModified = skeletonFile.lastModified(); - atlasModified = atlasFile == null ? 0 : atlasFile.lastModified(); + atlasModified = atlas.lastModified(); lastModifiedCheck = checkModifiedInterval; prefs.putString("lastFile", skeletonFile.path()); prefs.flush(); @@ -332,8 +230,7 @@ public class SkeletonViewer extends ApplicationAdapter { lastModifiedCheck = checkModifiedInterval; long time = skeletonFile.lastModified(); if (time != 0 && skeletonModified != time) reloadTimer = reloadDelay; - FileHandle atlasFile = atlasFile(skeletonFile); - time = atlasFile == null ? 0 : atlasFile.lastModified(); + time = atlas.lastModified(); if (time != 0 && atlasModified != time) reloadTimer = reloadDelay; } } else { @@ -346,8 +243,8 @@ public class SkeletonViewer extends ApplicationAdapter { // Pose and render skeleton. state.getData().setDefaultMix(ui.mixSlider.getValue()); - renderer.setPremultipliedAlpha(ui.premultipliedCheckbox.isChecked()); - batch.setPremultipliedAlpha(ui.premultipliedCheckbox.isChecked()); + renderer.setPremultipliedAlpha(ui.pmaCheckbox.isChecked()); + batch.setPremultipliedAlpha(ui.pmaCheckbox.isChecked()); float scaleX = ui.xScaleSlider.getValue(), scaleY = ui.yScaleSlider.getValue(); if (skeleton.scaleX == 0) skeleton.scaleX = 0.01f; @@ -441,678 +338,6 @@ public class SkeletonViewer extends ApplicationAdapter { if (!ui.minimizeButton.isChecked()) ui.window.setHeight(height / uiScale + 8); } - class UI { - boolean prefsLoaded; - - Stage stage = new Stage(new ScreenViewport()); - com.badlogic.gdx.scenes.scene2d.ui.Skin skin = new com.badlogic.gdx.scenes.scene2d.ui.Skin( - Gdx.files.internal("skin/skin.json")); - - Window window = new Window("Skeleton", skin); - Table root = new Table(skin); - TextButton openButton = new TextButton("Open", skin); - TextButton minimizeButton = new TextButton("-", skin); - - Slider loadScaleSlider = new Slider(0.1f, 3, 0.01f, false, skin); - Label loadScaleLabel = new Label("100%", skin); - TextButton loadScaleResetButton = new TextButton("Reload", skin); - - Slider zoomSlider = new Slider(0.01f, 10, 0.01f, false, skin); - Label zoomLabel = new Label("100%", skin); - TextButton zoomResetButton = new TextButton("Reset", skin); - - Slider xScaleSlider = new Slider(-2, 2, 0.01f, false, skin); - Label xScaleLabel = new Label("100%", skin); - TextButton xScaleResetButton = new TextButton("Reset", skin); - - Slider yScaleSlider = new Slider(-2, 2, 0.01f, false, skin); - Label yScaleLabel = new Label("100%", skin); - TextButton yScaleResetButton = new TextButton("Reset", skin); - - CheckBox debugBonesCheckbox = new CheckBox("Bones", skin); - CheckBox debugRegionsCheckbox = new CheckBox("Regions", skin); - CheckBox debugBoundingBoxesCheckbox = new CheckBox("Bounds", skin); - CheckBox debugMeshHullCheckbox = new CheckBox("Mesh hull", skin); - CheckBox debugMeshTrianglesCheckbox = new CheckBox("Triangles", skin); - CheckBox debugPathsCheckbox = new CheckBox("Paths", skin); - CheckBox debugPointsCheckbox = new CheckBox("Points", skin); - CheckBox debugClippingCheckbox = new CheckBox("Clipping", skin); - - CheckBox premultipliedCheckbox = new CheckBox("Premultiplied", skin); - - CheckBox linearCheckbox = new CheckBox("Linear", skin); - - TextButton bonesSetupPoseButton = new TextButton("Bones", skin); - TextButton slotsSetupPoseButton = new TextButton("Slots", skin); - TextButton setupPoseButton = new TextButton("Both", skin); - - List skinList = new List(skin); - ScrollPane skinScroll = new ScrollPane(skinList, skin, "bg"); - - ButtonGroup trackButtons = new ButtonGroup(); - CheckBox loopCheckbox = new CheckBox("Loop", skin); - CheckBox addCheckbox = new CheckBox("Add", skin); - - Slider alphaSlider = new Slider(0, 1, 0.01f, false, skin); - Label alphaLabel = new Label("100%", skin); - - List animationList = new List(skin); - ScrollPane animationScroll = new ScrollPane(animationList, skin, "bg"); - - Slider speedSlider = new Slider(0, 3, 0.01f, false, skin); - Label speedLabel = new Label("1.0x", skin); - TextButton speedResetButton = new TextButton("Reset", skin); - - CheckBox reverseCheckbox = new CheckBox("Reverse", skin); - CheckBox holdPrevCheckbox = new CheckBox("Hold previous", skin); - - Slider mixSlider = new Slider(0, 4, 0.01f, false, skin); - Label mixLabel = new Label("0.3s", skin); - - Label statusLabel = new Label("", skin); - WidgetGroup toasts = new WidgetGroup(); - - UI () { - initialize(); - layout(); - events(); - } - - void initialize () { - skin.getFont("default").getData().markupEnabled = true; - - for (int i = 0; i < 6; i++) - trackButtons.add(new TextButton(i + "", skin, "toggle")); - - premultipliedCheckbox.setChecked(true); - - linearCheckbox.setChecked(true); - - loopCheckbox.setChecked(true); - - loadScaleSlider.setValue(1); - loadScaleSlider.setSnapToValues(new float[] {0.5f, 1, 1.5f, 2, 2.5f}, 0.09f); - - zoomSlider.setValue(1); - zoomSlider.setSnapToValues(new float[] {1, 2}, 0.30f); - - xScaleSlider.setValue(1); - xScaleSlider.setSnapToValues(new float[] {-1.5f, -1, -0.5f, 0.5f, 1, 1.5f}, 0.12f); - - yScaleSlider.setValue(1); - yScaleSlider.setSnapToValues(new float[] {-1.5f, -1, -0.5f, 0.5f, 1, 1.5f}, 0.12f); - - skinList.getSelection().setRequired(false); - skinList.getSelection().setToggle(true); - - animationList.getSelection().setRequired(false); - animationList.getSelection().setToggle(true); - - mixSlider.setValue(0.3f); - mixSlider.setSnapToValues(new float[] {1, 1.5f, 2, 2.5f, 3, 3.5f}, 0.12f); - - speedSlider.setValue(1); - speedSlider.setSnapToValues(new float[] {0.5f, 0.75f, 1, 1.25f, 1.5f, 2, 2.5f}, 0.09f); - - alphaSlider.setValue(1); - alphaSlider.setDisabled(true); - - addCheckbox.setDisabled(true); - holdPrevCheckbox.setDisabled(true); - - window.setMovable(false); - window.setResizable(false); - window.setKeepWithinStage(false); - window.setX(-3); - window.setY(-2); - - window.getTitleLabel().setColor(new Color(0xc1ffffff)); - window.getTitleTable().add(openButton).space(3); - window.getTitleTable().add(minimizeButton).width(20); - - skinScroll.setFadeScrollBars(false); - - animationScroll.setFadeScrollBars(false); - } - - void layout () { - float resetWidth = loadScaleResetButton.getPrefWidth(); - - root.defaults().space(6); - root.columnDefaults(0).top().right().padTop(3); - root.columnDefaults(1).left(); - root.add("Load scale:"); - { - Table table = table(); - table.add(loadScaleLabel).width(29); - table.add(loadScaleSlider).growX(); - table.add(loadScaleResetButton).width(resetWidth); - root.add(table).fill().row(); - } - root.add("Zoom:"); - { - Table table = table(); - table.add(zoomLabel).width(29); - table.add(zoomSlider).growX(); - table.add(zoomResetButton).width(resetWidth); - root.add(table).fill().row(); - } - root.add("Scale X:"); - { - Table table = table(); - table.add(xScaleLabel).width(29); - table.add(xScaleSlider).growX(); - table.add(xScaleResetButton).width(resetWidth); - root.add(table).fill().row(); - } - root.add("Scale Y:"); - { - Table table = table(); - table.add(yScaleLabel).width(29); - table.add(yScaleSlider).growX(); - table.add(yScaleResetButton).width(resetWidth); - root.add(table).fill().row(); - } - root.add("Debug:"); - root.add(table(debugBonesCheckbox, debugRegionsCheckbox, debugBoundingBoxesCheckbox)).row(); - root.add(); - root.add(table(debugPathsCheckbox, debugPointsCheckbox, debugClippingCheckbox)).row(); - root.add(); - root.add(table(debugMeshHullCheckbox, debugMeshTrianglesCheckbox)).row(); - root.add("Atlas alpha:"); - { - Table table = table(); - table.add(premultipliedCheckbox); - table.add("Filtering:").growX().getActor().setAlignment(Align.right); - table.add(linearCheckbox); - root.add(table).fill().row(); - } - - root.add(new Image(skin.newDrawable("white", new Color(0x4e4e4eff)))).height(1).fillX().colspan(2).pad(-3, 0, 1, 0) - .row(); - - root.add("Setup pose:"); - root.add(table(bonesSetupPoseButton, slotsSetupPoseButton, setupPoseButton)).row(); - root.add("Skin:"); - root.add(skinScroll).grow().minHeight(64).row(); - - root.add(new Image(skin.newDrawable("white", new Color(0x4e4e4eff)))).height(1).fillX().colspan(2).pad(1, 0, 1, 0).row(); - - root.add("Track:"); - { - Table table = table(); - for (TextButton button : trackButtons.getButtons()) - table.add(button); - table.add(loopCheckbox); - root.add(table).row(); - } - root.add(); - { - Table table = table(); - table.add(reverseCheckbox); - table.add(holdPrevCheckbox); - table.add(addCheckbox); - root.add(table).row(); - } - root.add("Entry alpha:"); - { - Table table = table(); - table.add(alphaLabel).width(29); - table.add(alphaSlider).growX(); - root.add(table).fill().row(); - } - root.add("Animation:"); - root.add(animationScroll).grow().minHeight(64).row(); - - root.add(new Image(skin.newDrawable("white", new Color(0x4e4e4eff)))).height(1).fillX().colspan(2).pad(1, 0, 1, 0).row(); - - root.add("Speed:"); - { - Table table = table(); - table.add(speedLabel).width(29); - table.add(speedSlider).growX(); - table.add(speedResetButton); - root.add(table).fill().row(); - } - root.add("Default mix:"); - { - Table table = table(); - table.add(mixLabel).width(29); - table.add(mixSlider).growX(); - root.add(table).fill().row(); - } - - window.add(root).grow(); - window.pack(); - stage.addActor(window); - - stage.addActor(statusLabel); - - { - Table table = new Table(); - table.setFillParent(true); - table.setTouchable(Touchable.disabled); - stage.addActor(table); - table.pad(10, 10, 22, 10).bottom().right(); - table.add(toasts); - } - - { - Table table = new Table(); - table.setFillParent(true); - table.setTouchable(Touchable.disabled); - stage.addActor(table); - table.pad(10).top().right(); - table.defaults().right(); - table.add(new Label(version, skin, "default", Color.LIGHT_GRAY)); - } - } - - void events () { - window.addListener(new InputListener() { - public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { - event.cancel(); - return true; - } - }); - - openButton.addListener(new ChangeListener() { - public void changed (ChangeEvent event, Actor actor) { - FileDialog fileDialog = new FileDialog((Frame)null, "Choose skeleton file"); - fileDialog.setMode(FileDialog.LOAD); - fileDialog.setVisible(true); - String name = fileDialog.getFile(); - String dir = fileDialog.getDirectory(); - if (name == null || dir == null) return; - loadSkeleton(new FileHandle(new File(dir, name).getAbsolutePath())); - ui.toast("Loaded."); - } - }); - - setupPoseButton.addListener(new ChangeListener() { - public void changed (ChangeEvent event, Actor actor) { - if (skeleton != null) skeleton.setToSetupPose(); - } - }); - bonesSetupPoseButton.addListener(new ChangeListener() { - public void changed (ChangeEvent event, Actor actor) { - if (skeleton != null) skeleton.setBonesToSetupPose(); - } - }); - slotsSetupPoseButton.addListener(new ChangeListener() { - public void changed (ChangeEvent event, Actor actor) { - skeleton.getRootBone().getChildren(); - if (skeleton != null) skeleton.setSlotsToSetupPose(); - } - }); - - minimizeButton.addListener(new ClickListener() { - public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { - event.cancel(); - return super.touchDown(event, x, y, pointer, button); - } - - public void clicked (InputEvent event, float x, float y) { - if (minimizeButton.isChecked()) { - window.getCells().get(0).setActor(null); - window.setHeight(37); - minimizeButton.setText("+"); - } else { - window.getCells().get(0).setActor(root); - window.setHeight(Gdx.graphics.getHeight() / uiScale + 8); - minimizeButton.setText("-"); - } - } - }); - - loadScaleSlider.addListener(new ChangeListener() { - public void changed (ChangeEvent event, Actor actor) { - loadScaleLabel.setText(Integer.toString((int)(loadScaleSlider.getValue() * 100)) + "%"); - if (!loadScaleSlider.isDragging()) { - loadSkeleton(skeletonFile); - ui.toast("Reloaded."); - } - loadScaleResetButton.setText(loadScaleSlider.getValue() == 1 ? "Reload" : "Reset"); - } - }); - loadScaleResetButton.addListener(new ChangeListener() { - public void changed (ChangeEvent event, Actor actor) { - resetCameraPosition(); - if (loadScaleSlider.getValue() == 1) { - loadSkeleton(skeletonFile); - ui.toast("Reloaded."); - } else - loadScaleSlider.setValue(1); - loadScaleResetButton.setText("Reload"); - } - }); - - zoomSlider.addListener(new ChangeListener() { - public void changed (ChangeEvent event, Actor actor) { - zoomLabel.setText(Integer.toString((int)(zoomSlider.getValue() * 100)) + "%"); - float newZoom = 1 / zoomSlider.getValue(); - camera.position.x -= window.getWidth() / 2 * (newZoom - camera.zoom); - camera.zoom = newZoom; - } - }); - zoomResetButton.addListener(new ChangeListener() { - public void changed (ChangeEvent event, Actor actor) { - resetCameraPosition(); - float x = camera.position.x; - zoomSlider.setValue(1); - camera.position.x = x; - } - }); - - xScaleSlider.addListener(new ChangeListener() { - public void changed (ChangeEvent event, Actor actor) { - if (xScaleSlider.getValue() == 0) xScaleSlider.setValue(0.01f); - xScaleLabel.setText(Integer.toString((int)(xScaleSlider.getValue() * 100)) + "%"); - } - }); - xScaleResetButton.addListener(new ChangeListener() { - public void changed (ChangeEvent event, Actor actor) { - xScaleSlider.setValue(1); - } - }); - - yScaleSlider.addListener(new ChangeListener() { - public void changed (ChangeEvent event, Actor actor) { - if (yScaleSlider.getValue() == 0) yScaleSlider.setValue(0.01f); - yScaleLabel.setText(Integer.toString((int)(yScaleSlider.getValue() * 100)) + "%"); - } - }); - yScaleResetButton.addListener(new ChangeListener() { - public void changed (ChangeEvent event, Actor actor) { - yScaleSlider.setValue(1); - } - }); - - speedSlider.addListener(new ChangeListener() { - public void changed (ChangeEvent event, Actor actor) { - speedLabel.setText(Float.toString((int)(speedSlider.getValue() * 100) / 100f) + "x"); - } - }); - speedResetButton.addListener(new ChangeListener() { - public void changed (ChangeEvent event, Actor actor) { - speedSlider.setValue(1); - } - }); - - alphaSlider.addListener(new ChangeListener() { - public void changed (ChangeEvent event, Actor actor) { - alphaLabel.setText(Integer.toString((int)(alphaSlider.getValue() * 100)) + "%"); - int track = trackButtons.getCheckedIndex(); - if (track > 0) { - TrackEntry current = state.getCurrent(track); - if (current != null) { - current.setAlpha(alphaSlider.getValue()); - current.resetRotationDirections(); - } - } - } - }); - - mixSlider.addListener(new ChangeListener() { - public void changed (ChangeEvent event, Actor actor) { - mixLabel.setText(Float.toString((int)(mixSlider.getValue() * 100) / 100f) + "s"); - if (state != null) state.getData().setDefaultMix(mixSlider.getValue()); - } - }); - - InputListener scrollFocusListener = new InputListener() { - public void enter (InputEvent event, float x, float y, int pointer, @Null Actor fromActor) { - if (pointer == -1) stage.setScrollFocus(event.getListenerActor()); - } - - public void exit (InputEvent event, float x, float y, int pointer, @Null Actor toActor) { - if (pointer == -1 && stage.getScrollFocus() == event.getListenerActor()) stage.setScrollFocus(null); - } - }; - - animationList.addListener(new ChangeListener() { - public void changed (ChangeEvent event, Actor actor) { - if (state != null) { - String name = animationList.getSelected(); - if (name == null) - state.setEmptyAnimation(trackButtons.getCheckedIndex(), mixSlider.getValue()); - else - setAnimation(); - } - } - }); - animationScroll.addListener(scrollFocusListener); - - ChangeListener setAnimation = new ChangeListener() { - public void changed (ChangeEvent event, Actor actor) { - setAnimation(); - } - }; - loopCheckbox.addListener(setAnimation); - reverseCheckbox.addListener(setAnimation); - holdPrevCheckbox.addListener(setAnimation); - addCheckbox.addListener(setAnimation); - - linearCheckbox.addListener(new ChangeListener() { - public void changed (ChangeEvent event, Actor actor) { - if (atlas == null) return; - TextureFilter filter = linearCheckbox.isChecked() ? TextureFilter.Linear : TextureFilter.Nearest; - for (Texture texture : atlas.getTextures()) - texture.setFilter(filter, filter); - } - }); - - skinList.addListener(new ChangeListener() { - public void changed (ChangeEvent event, Actor actor) { - if (skeleton != null) { - String skinName = skinList.getSelected(); - if (skinName == null) - skeleton.setSkin((Skin)null); - else - skeleton.setSkin(skinName); - skeleton.setSlotsToSetupPose(); - } - } - }); - skinScroll.addListener(scrollFocusListener); - - ChangeListener trackButtonListener = new ChangeListener() { - public void changed (ChangeEvent event, Actor actor) { - int track = trackButtons.getCheckedIndex(); - if (track == -1) return; - TrackEntry current = state.getCurrent(track); - animationList.getSelection().setProgrammaticChangeEvents(false); - animationList.setSelected(current == null ? null : current.animation.name); - animationList.getSelection().setProgrammaticChangeEvents(true); - - alphaSlider.setDisabled(track == 0); - alphaSlider.setValue(current == null ? 1 : current.alpha); - - addCheckbox.setDisabled(track == 0); - holdPrevCheckbox.setDisabled(track == 0); - - if (current != null) { - loopCheckbox.setChecked(current.getLoop()); - addCheckbox.setChecked(current.getMixBlend() == MixBlend.add); - reverseCheckbox.setChecked(current.getReverse()); - holdPrevCheckbox.setChecked(current.getHoldPrevious()); - } - } - }; - for (TextButton button : trackButtons.getButtons()) - button.addListener(trackButtonListener); - - Gdx.input.setInputProcessor(new InputMultiplexer(stage, new InputAdapter() { - float offsetX; - float offsetY; - - public boolean touchDown (int screenX, int screenY, int pointer, int button) { - offsetX = screenX; - offsetY = Gdx.graphics.getHeight() - 1 - screenY; - return false; - } - - public boolean touchDragged (int screenX, int screenY, int pointer) { - float deltaX = screenX - offsetX; - float deltaY = Gdx.graphics.getHeight() - 1 - screenY - offsetY; - - camera.position.x -= deltaX * camera.zoom; - camera.position.y -= deltaY * camera.zoom; - - offsetX = screenX; - offsetY = Gdx.graphics.getHeight() - 1 - screenY; - return false; - } - - public boolean touchUp (int screenX, int screenY, int pointer, int button) { - savePrefs(); - return false; - } - - public boolean scrolled (float amountX, float amountY) { - float zoom = zoomSlider.getValue(), zoomMin = zoomSlider.getMinValue(), zoomMax = zoomSlider.getMaxValue(); - float speedAlpha = Math.min(1.2f, (zoom - zoomMin) / (zoomMax - zoomMin) * 3.5f); - zoom -= linear.apply(0.02f, 0.2f, speedAlpha) * Math.signum(amountY); - zoomSlider.setValue(MathUtils.clamp(zoom, zoomMin, zoomMax)); - return false; - } - })); - - ChangeListener savePrefsListener = new ChangeListener() { - public void changed (ChangeEvent event, Actor actor) { - if (actor instanceof Slider && ((Slider)actor).isDragging()) return; - savePrefs(); - } - }; - debugBonesCheckbox.addListener(savePrefsListener); - debugRegionsCheckbox.addListener(savePrefsListener); - debugMeshHullCheckbox.addListener(savePrefsListener); - debugMeshTrianglesCheckbox.addListener(savePrefsListener); - debugPathsCheckbox.addListener(savePrefsListener); - debugPointsCheckbox.addListener(savePrefsListener); - debugClippingCheckbox.addListener(savePrefsListener); - premultipliedCheckbox.addListener(savePrefsListener); - loopCheckbox.addListener(savePrefsListener); - addCheckbox.addListener(savePrefsListener); - holdPrevCheckbox.addListener(savePrefsListener); - reverseCheckbox.addListener(savePrefsListener); - speedSlider.addListener(savePrefsListener); - speedResetButton.addListener(savePrefsListener); - mixSlider.addListener(savePrefsListener); - loadScaleSlider.addListener(savePrefsListener); - loadScaleResetButton.addListener(savePrefsListener); - zoomSlider.addListener(savePrefsListener); - zoomResetButton.addListener(savePrefsListener); - animationList.addListener(savePrefsListener); - skinList.addListener(savePrefsListener); - } - - Table table (Actor... actors) { - Table table = new Table(skin); - table.defaults().space(6); - table.add(actors); - return table; - } - - void render () { - if (state != null && state.getCurrent(trackButtons.getCheckedIndex()) == null) { - animationList.getSelection().setProgrammaticChangeEvents(false); - animationList.setSelected(null); - animationList.getSelection().setProgrammaticChangeEvents(true); - } - - statusLabel.pack(); - if (minimizeButton.isChecked()) - statusLabel.setPosition(10, 25, Align.bottom | Align.left); - else - statusLabel.setPosition(window.getWidth() + 6, 5, Align.bottom | Align.left); - - stage.act(); - stage.draw(); - } - - void toast (String text) { - Table table = new Table(); - table.add(new Label(text, skin)); - table.getColor().a = 0; - table.pack(); - table.setPosition(-table.getWidth(), -3 - table.getHeight()); - table.addAction(sequence( // - parallel(moveBy(0, table.getHeight(), 0.3f), fadeIn(0.3f)), // - delay(5f), // - parallel(moveBy(0, table.getHeight(), 0.3f), fadeOut(0.3f)), // - removeActor() // - )); - for (Actor actor : toasts.getChildren()) - actor.addAction(moveBy(0, table.getHeight(), 0.3f)); - toasts.addActor(table); - toasts.getParent().toFront(); - } - - void savePrefs () { - if (!prefsLoaded) return; - prefs.putBoolean("debugBones", debugBonesCheckbox.isChecked()); - prefs.putBoolean("debugRegions", debugRegionsCheckbox.isChecked()); - prefs.putBoolean("debugMeshHull", debugMeshHullCheckbox.isChecked()); - prefs.putBoolean("debugMeshTriangles", debugMeshTrianglesCheckbox.isChecked()); - prefs.putBoolean("debugPaths", debugPathsCheckbox.isChecked()); - prefs.putBoolean("debugPoints", debugPointsCheckbox.isChecked()); - prefs.putBoolean("debugClipping", debugClippingCheckbox.isChecked()); - prefs.putBoolean("premultiplied", premultipliedCheckbox.isChecked()); - prefs.putBoolean("loop", loopCheckbox.isChecked()); - prefs.putBoolean("add", addCheckbox.isChecked()); - prefs.putBoolean("holdPrev", holdPrevCheckbox.isChecked()); - prefs.putBoolean("reverse", reverseCheckbox.isChecked()); - prefs.putFloat("speed", speedSlider.getValue()); - prefs.putFloat("mix", mixSlider.getValue()); - prefs.putFloat("scale", loadScaleSlider.getValue()); - prefs.putFloat("zoom", zoomSlider.getValue()); - prefs.putFloat("x", camera.position.x); - prefs.putFloat("y", camera.position.y); - if (state != null) { - TrackEntry current = state.getCurrent(0); - if (current != null) { - String name = current.animation.name; - if (name.equals("")) name = current.next == null ? "" : current.next.animation.name; - prefs.putString("animationName", name); - } - } - if (skinList.getSelected() != null) prefs.putString("skinName", skinList.getSelected()); - prefs.flush(); - } - - void loadPrefs () { - try { - debugBonesCheckbox.setChecked(prefs.getBoolean("debugBones", true)); - debugRegionsCheckbox.setChecked(prefs.getBoolean("debugRegions", false)); - debugMeshHullCheckbox.setChecked(prefs.getBoolean("debugMeshHull", false)); - debugMeshTrianglesCheckbox.setChecked(prefs.getBoolean("debugMeshTriangles", false)); - debugPathsCheckbox.setChecked(prefs.getBoolean("debugPaths", true)); - debugPointsCheckbox.setChecked(prefs.getBoolean("debugPoints", true)); - debugClippingCheckbox.setChecked(prefs.getBoolean("debugClipping", true)); - premultipliedCheckbox.setChecked(prefs.getBoolean("premultiplied", true)); - loopCheckbox.setChecked(prefs.getBoolean("loop", true)); - addCheckbox.setChecked(prefs.getBoolean("add", false)); - holdPrevCheckbox.setChecked(prefs.getBoolean("holdPrev", false)); - reverseCheckbox.setChecked(prefs.getBoolean("reverse", false)); - speedSlider.setValue(prefs.getFloat("speed", 0.3f)); - mixSlider.setValue(prefs.getFloat("mix", 0.3f)); - - zoomSlider.setValue(prefs.getFloat("zoom", 1)); - camera.zoom = 1 / prefs.getFloat("zoom", 1); - camera.position.x = prefs.getFloat("x", 0); - camera.position.y = prefs.getFloat("y", 0); - - loadScaleSlider.setValue(prefs.getFloat("scale", 1)); - animationList.setSelected(prefs.getString("animationName", null)); - skinList.setSelected(prefs.getString("skinName", null)); - } catch (Throwable ex) { - System.out.println("Unable to read preferences:"); - ex.printStackTrace(); - } - } - } - static public void main (String[] args) throws Exception { try { // Try to turn off illegal access log messages. Class loggerClass = Class.forName("jdk.internal.module.IllegalAccessLogger"); diff --git a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewerUI.java b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewerUI.java new file mode 100644 index 000000000..f0e16dad4 --- /dev/null +++ b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewerUI.java @@ -0,0 +1,723 @@ + +package com.esotericsoftware.spine; + +import static com.badlogic.gdx.math.Interpolation.*; +import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*; + +import java.io.File; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.InputAdapter; +import com.badlogic.gdx.InputMultiplexer; +import com.badlogic.gdx.Preferences; +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.OrthographicCamera; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.Texture.TextureFilter; +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.scenes.scene2d.Actor; +import com.badlogic.gdx.scenes.scene2d.InputEvent; +import com.badlogic.gdx.scenes.scene2d.InputListener; +import com.badlogic.gdx.scenes.scene2d.Stage; +import com.badlogic.gdx.scenes.scene2d.Touchable; +import com.badlogic.gdx.scenes.scene2d.ui.ButtonGroup; +import com.badlogic.gdx.scenes.scene2d.ui.CheckBox; +import com.badlogic.gdx.scenes.scene2d.ui.Image; +import com.badlogic.gdx.scenes.scene2d.ui.Label; +import com.badlogic.gdx.scenes.scene2d.ui.List; +import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane; +import com.badlogic.gdx.scenes.scene2d.ui.Slider; +import com.badlogic.gdx.scenes.scene2d.ui.Table; +import com.badlogic.gdx.scenes.scene2d.ui.TextButton; +import com.badlogic.gdx.scenes.scene2d.ui.WidgetGroup; +import com.badlogic.gdx.scenes.scene2d.ui.Window; +import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; +import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; +import com.badlogic.gdx.utils.Align; +import com.badlogic.gdx.utils.Null; +import com.badlogic.gdx.utils.viewport.ScreenViewport; + +import com.esotericsoftware.spine.Animation.MixBlend; +import com.esotericsoftware.spine.AnimationState.TrackEntry; + +import java.awt.FileDialog; +import java.awt.Frame; + +class SkeletonViewerUI { + final SkeletonViewer viewer; + final OrthographicCamera camera; + + boolean prefsLoaded; + + Stage stage = new Stage(new ScreenViewport()); + com.badlogic.gdx.scenes.scene2d.ui.Skin skin = new com.badlogic.gdx.scenes.scene2d.ui.Skin( + Gdx.files.internal("skin/skin.json")); + + Window window = new Window("Skeleton", skin); + Table root = new Table(skin); + TextButton openButton = new TextButton("Open", skin); + TextButton minimizeButton = new TextButton("-", skin); + + Slider loadScaleSlider = new Slider(0.1f, 3, 0.01f, false, skin); + Label loadScaleLabel = new Label("100%", skin); + TextButton loadScaleResetButton = new TextButton("Reload", skin); + + Slider zoomSlider = new Slider(0.01f, 10, 0.01f, false, skin); + Label zoomLabel = new Label("100%", skin); + TextButton zoomResetButton = new TextButton("Reset", skin); + + Slider xScaleSlider = new Slider(-2, 2, 0.01f, false, skin); + Label xScaleLabel = new Label("100%", skin); + TextButton xScaleResetButton = new TextButton("Reset", skin); + + Slider yScaleSlider = new Slider(-2, 2, 0.01f, false, skin); + Label yScaleLabel = new Label("100%", skin); + TextButton yScaleResetButton = new TextButton("Reset", skin); + + CheckBox debugBonesCheckbox = new CheckBox("Bones", skin); + CheckBox debugRegionsCheckbox = new CheckBox("Regions", skin); + CheckBox debugBoundingBoxesCheckbox = new CheckBox("Bounds", skin); + CheckBox debugMeshHullCheckbox = new CheckBox("Mesh hull", skin); + CheckBox debugMeshTrianglesCheckbox = new CheckBox("Triangles", skin); + CheckBox debugPathsCheckbox = new CheckBox("Paths", skin); + CheckBox debugPointsCheckbox = new CheckBox("Points", skin); + CheckBox debugClippingCheckbox = new CheckBox("Clipping", skin); + + CheckBox pmaCheckbox = new CheckBox("Premultiplied", skin); + + CheckBox linearCheckbox = new CheckBox("Linear", skin); + + TextButton bonesSetupPoseButton = new TextButton("Bones", skin); + TextButton slotsSetupPoseButton = new TextButton("Slots", skin); + TextButton setupPoseButton = new TextButton("Both", skin); + + List skinList = new List(skin); + ScrollPane skinScroll = new ScrollPane(skinList, skin, "bg"); + + ButtonGroup trackButtons = new ButtonGroup(); + CheckBox loopCheckbox = new CheckBox("Loop", skin); + CheckBox addCheckbox = new CheckBox("Add", skin); + + Slider alphaSlider = new Slider(0, 1, 0.01f, false, skin); + Label alphaLabel = new Label("100%", skin); + + List animationList = new List(skin); + ScrollPane animationScroll = new ScrollPane(animationList, skin, "bg"); + + Slider speedSlider = new Slider(0, 3, 0.01f, false, skin); + Label speedLabel = new Label("1.0x", skin); + TextButton speedResetButton = new TextButton("Reset", skin); + + CheckBox reverseCheckbox = new CheckBox("Reverse", skin); + CheckBox holdPrevCheckbox = new CheckBox("Hold previous", skin); + + Slider mixSlider = new Slider(0, 4, 0.01f, false, skin); + Label mixLabel = new Label("0.3s", skin); + + Label statusLabel = new Label("", skin); + WidgetGroup toasts = new WidgetGroup(); + + SkeletonViewerUI (SkeletonViewer viewer) { + this.viewer = viewer; + camera = viewer.camera; + initialize(); + layout(); + events(); + } + + void initialize () { + skin.getFont("default").getData().markupEnabled = true; + + for (int i = 0; i < 6; i++) + trackButtons.add(new TextButton(i + "", skin, "toggle")); + + pmaCheckbox.setChecked(true); + + linearCheckbox.setChecked(true); + + loopCheckbox.setChecked(true); + + loadScaleSlider.setValue(1); + loadScaleSlider.setSnapToValues(new float[] {0.5f, 1, 1.5f, 2, 2.5f}, 0.09f); + + zoomSlider.setValue(1); + zoomSlider.setSnapToValues(new float[] {1, 2}, 0.30f); + + xScaleSlider.setValue(1); + xScaleSlider.setSnapToValues(new float[] {-1.5f, -1, -0.5f, 0.5f, 1, 1.5f}, 0.12f); + + yScaleSlider.setValue(1); + yScaleSlider.setSnapToValues(new float[] {-1.5f, -1, -0.5f, 0.5f, 1, 1.5f}, 0.12f); + + skinList.getSelection().setRequired(false); + skinList.getSelection().setToggle(true); + + animationList.getSelection().setRequired(false); + animationList.getSelection().setToggle(true); + + mixSlider.setValue(0.3f); + mixSlider.setSnapToValues(new float[] {1, 1.5f, 2, 2.5f, 3, 3.5f}, 0.12f); + + speedSlider.setValue(1); + speedSlider.setSnapToValues(new float[] {0.5f, 0.75f, 1, 1.25f, 1.5f, 2, 2.5f}, 0.09f); + + alphaSlider.setValue(1); + alphaSlider.setDisabled(true); + + addCheckbox.setDisabled(true); + holdPrevCheckbox.setDisabled(true); + + window.setMovable(false); + window.setResizable(false); + window.setKeepWithinStage(false); + window.setX(-3); + window.setY(-2); + + window.getTitleLabel().setColor(new Color(0xc1ffffff)); + window.getTitleTable().add(openButton).space(3); + window.getTitleTable().add(minimizeButton).width(20); + + skinScroll.setFadeScrollBars(false); + + animationScroll.setFadeScrollBars(false); + } + + void layout () { + float resetWidth = loadScaleResetButton.getPrefWidth(); + + root.defaults().space(6); + root.columnDefaults(0).top().right().padTop(3); + root.columnDefaults(1).left(); + root.add("Load scale:"); + { + Table table = table(); + table.add(loadScaleLabel).width(29); + table.add(loadScaleSlider).growX(); + table.add(loadScaleResetButton).width(resetWidth); + root.add(table).fill().row(); + } + root.add("Zoom:"); + { + Table table = table(); + table.add(zoomLabel).width(29); + table.add(zoomSlider).growX(); + table.add(zoomResetButton).width(resetWidth); + root.add(table).fill().row(); + } + root.add("Scale X:"); + { + Table table = table(); + table.add(xScaleLabel).width(29); + table.add(xScaleSlider).growX(); + table.add(xScaleResetButton).width(resetWidth); + root.add(table).fill().row(); + } + root.add("Scale Y:"); + { + Table table = table(); + table.add(yScaleLabel).width(29); + table.add(yScaleSlider).growX(); + table.add(yScaleResetButton).width(resetWidth); + root.add(table).fill().row(); + } + root.add("Debug:"); + root.add(table(debugBonesCheckbox, debugRegionsCheckbox, debugBoundingBoxesCheckbox)).row(); + root.add(); + root.add(table(debugPathsCheckbox, debugPointsCheckbox, debugClippingCheckbox)).row(); + root.add(); + root.add(table(debugMeshHullCheckbox, debugMeshTrianglesCheckbox)).row(); + root.add("Atlas alpha:"); + { + Table table = table(); + table.add(pmaCheckbox); + table.add("Filtering:").growX().getActor().setAlignment(Align.right); + table.add(linearCheckbox); + root.add(table).fill().row(); + } + + root.add(new Image(skin.newDrawable("white", new Color(0x4e4e4eff)))).height(1).fillX().colspan(2).pad(-3, 0, 1, 0).row(); + + root.add("Setup pose:"); + root.add(table(bonesSetupPoseButton, slotsSetupPoseButton, setupPoseButton)).row(); + root.add("Skin:"); + root.add(skinScroll).grow().minHeight(64).row(); + + root.add(new Image(skin.newDrawable("white", new Color(0x4e4e4eff)))).height(1).fillX().colspan(2).pad(1, 0, 1, 0).row(); + + root.add("Track:"); + { + Table table = table(); + for (TextButton button : trackButtons.getButtons()) + table.add(button); + table.add(loopCheckbox); + root.add(table).row(); + } + root.add(); + { + Table table = table(); + table.add(reverseCheckbox); + table.add(holdPrevCheckbox); + table.add(addCheckbox); + root.add(table).row(); + } + root.add("Entry alpha:"); + { + Table table = table(); + table.add(alphaLabel).width(29); + table.add(alphaSlider).growX(); + root.add(table).fill().row(); + } + root.add("Animation:"); + root.add(animationScroll).grow().minHeight(64).row(); + + root.add(new Image(skin.newDrawable("white", new Color(0x4e4e4eff)))).height(1).fillX().colspan(2).pad(1, 0, 1, 0).row(); + + root.add("Speed:"); + { + Table table = table(); + table.add(speedLabel).width(29); + table.add(speedSlider).growX(); + table.add(speedResetButton); + root.add(table).fill().row(); + } + root.add("Default mix:"); + { + Table table = table(); + table.add(mixLabel).width(29); + table.add(mixSlider).growX(); + root.add(table).fill().row(); + } + + window.add(root).grow(); + window.pack(); + stage.addActor(window); + + stage.addActor(statusLabel); + + { + Table table = new Table(); + table.setFillParent(true); + table.setTouchable(Touchable.disabled); + stage.addActor(table); + table.pad(10, 10, 22, 10).bottom().right(); + table.add(toasts); + } + + { + Table table = new Table(); + table.setFillParent(true); + table.setTouchable(Touchable.disabled); + stage.addActor(table); + table.pad(10).top().right(); + table.defaults().right(); + table.add(new Label(SkeletonViewer.version, skin, "default", Color.LIGHT_GRAY)); + } + } + + void events () { + window.addListener(new InputListener() { + public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { + event.cancel(); + return true; + } + }); + + openButton.addListener(new ChangeListener() { + public void changed (ChangeEvent event, Actor actor) { + FileDialog fileDialog = new FileDialog((Frame)null, "Choose skeleton file"); + fileDialog.setMode(FileDialog.LOAD); + fileDialog.setVisible(true); + String name = fileDialog.getFile(); + String dir = fileDialog.getDirectory(); + if (name == null || dir == null) return; + viewer.loadSkeleton(new FileHandle(new File(dir, name).getAbsolutePath())); + toast("Loaded."); + } + }); + + setupPoseButton.addListener(new ChangeListener() { + public void changed (ChangeEvent event, Actor actor) { + if (viewer.skeleton != null) viewer.skeleton.setToSetupPose(); + } + }); + bonesSetupPoseButton.addListener(new ChangeListener() { + public void changed (ChangeEvent event, Actor actor) { + if (viewer.skeleton != null) viewer.skeleton.setBonesToSetupPose(); + } + }); + slotsSetupPoseButton.addListener(new ChangeListener() { + public void changed (ChangeEvent event, Actor actor) { + viewer.skeleton.getRootBone().getChildren(); + if (viewer.skeleton != null) viewer.skeleton.setSlotsToSetupPose(); + } + }); + + minimizeButton.addListener(new ClickListener() { + public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { + event.cancel(); + return super.touchDown(event, x, y, pointer, button); + } + + public void clicked (InputEvent event, float x, float y) { + if (minimizeButton.isChecked()) { + window.getCells().get(0).setActor(null); + window.setHeight(37); + minimizeButton.setText("+"); + } else { + window.getCells().get(0).setActor(root); + window.setHeight(Gdx.graphics.getHeight() / SkeletonViewer.uiScale + 8); + minimizeButton.setText("-"); + } + } + }); + + loadScaleSlider.addListener(new ChangeListener() { + public void changed (ChangeEvent event, Actor actor) { + loadScaleLabel.setText(Integer.toString((int)(loadScaleSlider.getValue() * 100)) + "%"); + if (!loadScaleSlider.isDragging()) { + viewer.loadSkeleton(viewer.skeletonFile); + toast("Reloaded."); + } + loadScaleResetButton.setText(loadScaleSlider.getValue() == 1 ? "Reload" : "Reset"); + } + }); + loadScaleResetButton.addListener(new ChangeListener() { + public void changed (ChangeEvent event, Actor actor) { + viewer.resetCameraPosition(); + if (loadScaleSlider.getValue() == 1) { + viewer.loadSkeleton(viewer.skeletonFile); + toast("Reloaded."); + } else + loadScaleSlider.setValue(1); + loadScaleResetButton.setText("Reload"); + } + }); + + zoomSlider.addListener(new ChangeListener() { + public void changed (ChangeEvent event, Actor actor) { + zoomLabel.setText(Integer.toString((int)(zoomSlider.getValue() * 100)) + "%"); + float newZoom = 1 / zoomSlider.getValue(); + camera.position.x -= window.getWidth() / 2 * (newZoom - camera.zoom); + camera.zoom = newZoom; + } + }); + zoomResetButton.addListener(new ChangeListener() { + public void changed (ChangeEvent event, Actor actor) { + viewer.resetCameraPosition(); + float x = camera.position.x; + zoomSlider.setValue(1); + camera.position.x = x; + } + }); + + xScaleSlider.addListener(new ChangeListener() { + public void changed (ChangeEvent event, Actor actor) { + if (xScaleSlider.getValue() == 0) xScaleSlider.setValue(0.01f); + xScaleLabel.setText(Integer.toString((int)(xScaleSlider.getValue() * 100)) + "%"); + } + }); + xScaleResetButton.addListener(new ChangeListener() { + public void changed (ChangeEvent event, Actor actor) { + xScaleSlider.setValue(1); + } + }); + + yScaleSlider.addListener(new ChangeListener() { + public void changed (ChangeEvent event, Actor actor) { + if (yScaleSlider.getValue() == 0) yScaleSlider.setValue(0.01f); + yScaleLabel.setText(Integer.toString((int)(yScaleSlider.getValue() * 100)) + "%"); + } + }); + yScaleResetButton.addListener(new ChangeListener() { + public void changed (ChangeEvent event, Actor actor) { + yScaleSlider.setValue(1); + } + }); + + speedSlider.addListener(new ChangeListener() { + public void changed (ChangeEvent event, Actor actor) { + speedLabel.setText(Float.toString((int)(speedSlider.getValue() * 100) / 100f) + "x"); + } + }); + speedResetButton.addListener(new ChangeListener() { + public void changed (ChangeEvent event, Actor actor) { + speedSlider.setValue(1); + } + }); + + alphaSlider.addListener(new ChangeListener() { + public void changed (ChangeEvent event, Actor actor) { + alphaLabel.setText(Integer.toString((int)(alphaSlider.getValue() * 100)) + "%"); + int track = trackButtons.getCheckedIndex(); + if (track > 0) { + TrackEntry current = viewer.state.getCurrent(track); + if (current != null) { + current.setAlpha(alphaSlider.getValue()); + current.resetRotationDirections(); + } + } + } + }); + + mixSlider.addListener(new ChangeListener() { + public void changed (ChangeEvent event, Actor actor) { + mixLabel.setText(Float.toString((int)(mixSlider.getValue() * 100) / 100f) + "s"); + if (viewer.state != null) viewer.state.getData().setDefaultMix(mixSlider.getValue()); + } + }); + + InputListener scrollFocusListener = new InputListener() { + public void enter (InputEvent event, float x, float y, int pointer, @Null Actor fromActor) { + if (pointer == -1) stage.setScrollFocus(event.getListenerActor()); + } + + public void exit (InputEvent event, float x, float y, int pointer, @Null Actor toActor) { + if (pointer == -1 && stage.getScrollFocus() == event.getListenerActor()) stage.setScrollFocus(null); + } + }; + + animationList.addListener(new ChangeListener() { + public void changed (ChangeEvent event, Actor actor) { + if (viewer.state != null) { + String name = animationList.getSelected(); + if (name == null) + viewer.state.setEmptyAnimation(trackButtons.getCheckedIndex(), mixSlider.getValue()); + else + viewer.setAnimation(); + } + } + }); + animationScroll.addListener(scrollFocusListener); + + ChangeListener setAnimation = new ChangeListener() { + public void changed (ChangeEvent event, Actor actor) { + viewer.setAnimation(); + } + }; + loopCheckbox.addListener(setAnimation); + reverseCheckbox.addListener(setAnimation); + holdPrevCheckbox.addListener(setAnimation); + addCheckbox.addListener(setAnimation); + + linearCheckbox.addListener(new ChangeListener() { + public void changed (ChangeEvent event, Actor actor) { + if (viewer.atlas == null) return; + TextureFilter filter = linearCheckbox.isChecked() ? TextureFilter.Linear : TextureFilter.Nearest; + for (Texture texture : viewer.atlas.getTextures()) + texture.setFilter(filter, filter); + } + }); + + skinList.addListener(new ChangeListener() { + public void changed (ChangeEvent event, Actor actor) { + if (viewer.skeleton != null) { + String skinName = skinList.getSelected(); + if (skinName == null) + viewer.skeleton.setSkin((Skin)null); + else + viewer.skeleton.setSkin(skinName); + viewer.skeleton.setSlotsToSetupPose(); + } + } + }); + skinScroll.addListener(scrollFocusListener); + + ChangeListener trackButtonListener = new ChangeListener() { + public void changed (ChangeEvent event, Actor actor) { + int track = trackButtons.getCheckedIndex(); + if (track == -1) return; + TrackEntry current = viewer.state.getCurrent(track); + animationList.getSelection().setProgrammaticChangeEvents(false); + animationList.setSelected(current == null ? null : current.animation.name); + animationList.getSelection().setProgrammaticChangeEvents(true); + + alphaSlider.setDisabled(track == 0); + alphaSlider.setValue(current == null ? 1 : current.alpha); + + addCheckbox.setDisabled(track == 0); + holdPrevCheckbox.setDisabled(track == 0); + + if (current != null) { + loopCheckbox.setChecked(current.getLoop()); + addCheckbox.setChecked(current.getMixBlend() == MixBlend.add); + reverseCheckbox.setChecked(current.getReverse()); + holdPrevCheckbox.setChecked(current.getHoldPrevious()); + } + } + }; + for (TextButton button : trackButtons.getButtons()) + button.addListener(trackButtonListener); + + Gdx.input.setInputProcessor(new InputMultiplexer(stage, new InputAdapter() { + float offsetX; + float offsetY; + + public boolean touchDown (int screenX, int screenY, int pointer, int button) { + offsetX = screenX; + offsetY = Gdx.graphics.getHeight() - 1 - screenY; + return false; + } + + public boolean touchDragged (int screenX, int screenY, int pointer) { + float deltaX = screenX - offsetX; + float deltaY = Gdx.graphics.getHeight() - 1 - screenY - offsetY; + + camera.position.x -= deltaX * camera.zoom; + camera.position.y -= deltaY * camera.zoom; + + offsetX = screenX; + offsetY = Gdx.graphics.getHeight() - 1 - screenY; + return false; + } + + public boolean touchUp (int screenX, int screenY, int pointer, int button) { + savePrefs(); + return false; + } + + public boolean scrolled (float amountX, float amountY) { + float zoom = zoomSlider.getValue(), zoomMin = zoomSlider.getMinValue(), zoomMax = zoomSlider.getMaxValue(); + float speedAlpha = Math.min(1.2f, (zoom - zoomMin) / (zoomMax - zoomMin) * 3.5f); + zoom -= linear.apply(0.02f, 0.2f, speedAlpha) * Math.signum(amountY); + zoomSlider.setValue(MathUtils.clamp(zoom, zoomMin, zoomMax)); + return false; + } + })); + + ChangeListener savePrefsListener = new ChangeListener() { + public void changed (ChangeEvent event, Actor actor) { + if (actor instanceof Slider && ((Slider)actor).isDragging()) return; + savePrefs(); + } + }; + debugBonesCheckbox.addListener(savePrefsListener); + debugRegionsCheckbox.addListener(savePrefsListener); + debugMeshHullCheckbox.addListener(savePrefsListener); + debugMeshTrianglesCheckbox.addListener(savePrefsListener); + debugPathsCheckbox.addListener(savePrefsListener); + debugPointsCheckbox.addListener(savePrefsListener); + debugClippingCheckbox.addListener(savePrefsListener); + pmaCheckbox.addListener(savePrefsListener); + loopCheckbox.addListener(savePrefsListener); + addCheckbox.addListener(savePrefsListener); + holdPrevCheckbox.addListener(savePrefsListener); + reverseCheckbox.addListener(savePrefsListener); + speedSlider.addListener(savePrefsListener); + speedResetButton.addListener(savePrefsListener); + mixSlider.addListener(savePrefsListener); + loadScaleSlider.addListener(savePrefsListener); + loadScaleResetButton.addListener(savePrefsListener); + zoomSlider.addListener(savePrefsListener); + zoomResetButton.addListener(savePrefsListener); + animationList.addListener(savePrefsListener); + skinList.addListener(savePrefsListener); + } + + Table table (Actor... actors) { + Table table = new Table(skin); + table.defaults().space(6); + table.add(actors); + return table; + } + + void render () { + if (viewer.state != null && viewer.state.getCurrent(trackButtons.getCheckedIndex()) == null) { + animationList.getSelection().setProgrammaticChangeEvents(false); + animationList.setSelected(null); + animationList.getSelection().setProgrammaticChangeEvents(true); + } + + statusLabel.pack(); + if (minimizeButton.isChecked()) + statusLabel.setPosition(10, 25, Align.bottom | Align.left); + else + statusLabel.setPosition(window.getWidth() + 6, 5, Align.bottom | Align.left); + + stage.act(); + stage.draw(); + } + + void toast (String text) { + Table table = new Table(); + table.add(new Label(text, skin)); + table.getColor().a = 0; + table.pack(); + table.setPosition(-table.getWidth(), -3 - table.getHeight()); + table.addAction(sequence( // + parallel(moveBy(0, table.getHeight(), 0.3f), fadeIn(0.3f)), // + delay(5f), // + parallel(moveBy(0, table.getHeight(), 0.3f), fadeOut(0.3f)), // + removeActor() // + )); + for (Actor actor : toasts.getChildren()) + actor.addAction(moveBy(0, table.getHeight(), 0.3f)); + toasts.addActor(table); + toasts.getParent().toFront(); + } + + void savePrefs () { + if (!prefsLoaded) return; + Preferences prefs = viewer.prefs; + prefs.putBoolean("debugBones", debugBonesCheckbox.isChecked()); + prefs.putBoolean("debugRegions", debugRegionsCheckbox.isChecked()); + prefs.putBoolean("debugMeshHull", debugMeshHullCheckbox.isChecked()); + prefs.putBoolean("debugMeshTriangles", debugMeshTrianglesCheckbox.isChecked()); + prefs.putBoolean("debugPaths", debugPathsCheckbox.isChecked()); + prefs.putBoolean("debugPoints", debugPointsCheckbox.isChecked()); + prefs.putBoolean("debugClipping", debugClippingCheckbox.isChecked()); + prefs.putBoolean("premultiplied", pmaCheckbox.isChecked()); + prefs.putBoolean("loop", loopCheckbox.isChecked()); + prefs.putBoolean("add", addCheckbox.isChecked()); + prefs.putBoolean("holdPrev", holdPrevCheckbox.isChecked()); + prefs.putBoolean("reverse", reverseCheckbox.isChecked()); + prefs.putFloat("speed", speedSlider.getValue()); + prefs.putFloat("mix", mixSlider.getValue()); + prefs.putFloat("scale", loadScaleSlider.getValue()); + prefs.putFloat("zoom", zoomSlider.getValue()); + prefs.putFloat("x", camera.position.x); + prefs.putFloat("y", camera.position.y); + if (viewer.state != null) { + TrackEntry current = viewer.state.getCurrent(0); + if (current != null) { + String name = current.animation.name; + if (name.equals("")) name = current.next == null ? "" : current.next.animation.name; + prefs.putString("animationName", name); + } + } + if (skinList.getSelected() != null) prefs.putString("skinName", skinList.getSelected()); + prefs.flush(); + } + + void loadPrefs () { + try { + Preferences prefs = viewer.prefs; + debugBonesCheckbox.setChecked(prefs.getBoolean("debugBones", true)); + debugRegionsCheckbox.setChecked(prefs.getBoolean("debugRegions", false)); + debugMeshHullCheckbox.setChecked(prefs.getBoolean("debugMeshHull", false)); + debugMeshTrianglesCheckbox.setChecked(prefs.getBoolean("debugMeshTriangles", false)); + debugPathsCheckbox.setChecked(prefs.getBoolean("debugPaths", true)); + debugPointsCheckbox.setChecked(prefs.getBoolean("debugPoints", true)); + debugClippingCheckbox.setChecked(prefs.getBoolean("debugClipping", true)); + pmaCheckbox.setChecked(prefs.getBoolean("premultiplied", true)); + loopCheckbox.setChecked(prefs.getBoolean("loop", true)); + addCheckbox.setChecked(prefs.getBoolean("add", false)); + holdPrevCheckbox.setChecked(prefs.getBoolean("holdPrev", false)); + reverseCheckbox.setChecked(prefs.getBoolean("reverse", false)); + speedSlider.setValue(prefs.getFloat("speed", 0.3f)); + mixSlider.setValue(prefs.getFloat("mix", 0.3f)); + + zoomSlider.setValue(prefs.getFloat("zoom", 1)); + camera.zoom = 1 / prefs.getFloat("zoom", 1); + camera.position.x = prefs.getFloat("x", 0); + camera.position.y = prefs.getFloat("y", 0); + + loadScaleSlider.setValue(prefs.getFloat("scale", 1)); + animationList.setSelected(prefs.getString("animationName", null)); + skinList.setSelected(prefs.getString("skinName", null)); + } catch (Throwable ex) { + System.out.println("Unable to read preferences:"); + ex.printStackTrace(); + } + } +}