package net.alcuria.umbracraft.editor.modules; import java.util.HashSet; import java.util.Set; import net.alcuria.umbracraft.definitions.anim.AnimationDefinition; import net.alcuria.umbracraft.definitions.anim.AnimationFrameDefinition; import net.alcuria.umbracraft.definitions.anim.AnimationListDefinition; import net.alcuria.umbracraft.editor.Editor; import net.alcuria.umbracraft.editor.widget.AnimationPreview; import net.alcuria.umbracraft.editor.widget.AnimationPreviewFrame; import net.alcuria.umbracraft.editor.widget.WidgetUtils; import net.alcuria.umbracraft.listeners.Listener; import net.alcuria.umbracraft.listeners.TypeListener; import net.alcuria.umbracraft.util.FileUtils; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.ui.Image; import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane; import com.badlogic.gdx.scenes.scene2d.ui.Table; import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Json; import com.badlogic.gdx.utils.JsonWriter.OutputType; import com.badlogic.gdx.utils.ObjectMap; import com.kotcrab.vis.ui.widget.VisLabel; import com.kotcrab.vis.ui.widget.VisTextButton; import com.kotcrab.vis.ui.widget.color.ColorPicker; import com.kotcrab.vis.ui.widget.color.ColorPickerListener; /** The module for all the animations. * @author Andrew Keturi */ public class AnimationsModule extends Module<AnimationListDefinition> { private final Table animationListContents = new Table(); private final Array<VisTextButton> buttons = new Array<VisTextButton>(); private final ColorPicker colorPicker = new ColorPicker(); private Table currentAnimTable; private final Set<String> expandedTags = new HashSet<String>(); private AnimationFrameDefinition frameColorChanging; private String highlightedButton; private Table previewTable, colorPickerTable; private final Table scroll = new Table(); private final Array<AnimationDefinition> sortedDefinitions = new Array<AnimationDefinition>(); public AnimationsModule() { super(); load(AnimationListDefinition.class); if (rootDefinition == null) { rootDefinition = new AnimationListDefinition(); } colorPicker.setListener(new ColorPickerListener() { @Override public void canceled() { } @Override public void finished(Color newColor) { if (frameColorChanging != null) { frameColorChanging.color = newColor; } } }); } private void addAnimationList(final Table content) { buttons.clear(); ScrollPane scroll = new ScrollPane(animationListContents); updateAnimationList(content); content.add(scroll).expandY().fill().padLeft(5); } private void createCurrentAnimTable(final Table content, final Table table, final AnimationDefinition definition, final VisTextButton button) { table.add(new Table() { { final Image image = new Image(); add(image); add(previewTable = new Table() { { add(new AnimationPreview(definition)).size(definition.width * 2, definition.height * 2).pad(30); } }); if (definition.filename != null && definition.filename.length() > 0) { String path = "sprites/animations/" + definition.filename + ".png"; String extPath = Editor.db().config().projectPath + Editor.db().config().spritePath + definition.filename + ".png"; if (Gdx.files.internal(path).exists()) { image.setDrawable(new TextureRegionDrawable(new TextureRegion(new Texture(Gdx.files.internal(path))))); } else if (Gdx.files.absolute(extPath).exists()) { image.setDrawable(new TextureRegionDrawable(new TextureRegion(new Texture(Gdx.files.absolute(extPath))))); } } populate(this, AnimationDefinition.class, definition, animationPopulateConfig(image)); row(); final VisTextButton deleteButton = new VisTextButton("Delete Animation"); deleteButton.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { rootDefinition.delete(definition); content.clear(); populate(content); }; }); final VisTextButton cloneButton = new VisTextButton("Clone Animation"); cloneButton.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { rootDefinition.clone(definition); content.clear(); populate(content); }; }); add(new Table() { { add(cloneButton).row(); add(deleteButton); } }); } private PopulateConfig animationPopulateConfig(Image image) { PopulateConfig cfg = new PopulateConfig(); cfg.listener = updateButtonListener(image, button, scroll, definition); cfg.cols = 1; cfg.suggestions = new ObjectMap<String, Array<String>>(); cfg.suggestions.put("tag", new Array<String>() { { for (AnimationDefinition def : rootDefinition.animations.values()) { if (!contains(def.tag, false)) { add(def.tag); } } } }); cfg.suggestions.put("filename", FileUtils.getFilesAt(Editor.db().config().projectPath + Editor.db().config().spritePath, true)); cfg.labelWidth = 80; cfg.textFieldWidth = 200; return cfg; } }).row(); scroll.clear(); scroll.add(createFrames(scroll, definition)); table.add(scroll).row(); table.add(new Table() { { final VisTextButton addFrameButton = new VisTextButton("Add Frame"); addFrameButton.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { definition.frames.add(new AnimationFrameDefinition()); scroll.clear(); scroll.add(createFrames(scroll, definition)); updatePreviewAnimation(definition); } }); add(addFrameButton).padBottom(20); } }).expandY().top(); } private Table createFrames(final Table scroll, final AnimationDefinition definition) { return new Table() { { stack(new Table() { { if (definition.frames == null) { definition.frames = new Array<AnimationFrameDefinition>(); } for (int i = 0; i < definition.frames.size; i++) { final int idx = i; if (idx != 0) { WidgetUtils.divider(this, "blue"); } add(new Table() { { defaults().pad(5, 20, 5, 20); final AnimationFrameDefinition frame = definition.frames.get(idx); final AnimationPreviewFrame image = new AnimationPreviewFrame(definition, frame) { @Override public void act(float delta) { super.act(delta); setColor(frame.color != null ? frame.color : Color.WHITE); } }; add(image).size(definition.width * 2, definition.height * 2); add(new VisLabel("Frame " + (idx + 1))); populate(this, AnimationFrameDefinition.class, frame, frameConfig(image, frame)); add(WidgetUtils.button("Color", new Listener() { @Override public void invoke() { frameColorChanging = frame; colorPickerTable.clear(); colorPickerTable.add(colorPicker); colorPicker.fadeIn(); colorPicker.setColor(frame.color != null ? frame.color : Color.WHITE); } })); add(new VisTextButton("Delete") { { addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { if (idx < definition.frames.size && idx >= 0) { definition.frames.removeIndex(idx); } scroll.clear(); scroll.add(createFrames(scroll, definition)); }; }); } }); add(new VisTextButton("Clone") { { addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { if (idx < definition.frames.size && idx >= 0) { definition.frames.insert(idx, definition.frames.get(idx).copy()); } scroll.clear(); scroll.add(createFrames(scroll, definition)); }; }); } }); } private PopulateConfig frameConfig(final AnimationPreviewFrame image, final AnimationFrameDefinition frame) { PopulateConfig cfg = new PopulateConfig(); cfg.listener = new TypeListener<String>() { @Override public void invoke(String type) { image.update(definition, frame); } }; cfg.cols = 3; cfg.labelWidth = 30; cfg.textFieldWidth = 30; cfg.suggestions = new ObjectMap<String, Array<String>>(); cfg.suggestions.put("particle", FileUtils.getFilesAt(Editor.db().config().projectPath + Editor.db().config().particlePath, false)); return cfg; } }).row(); } } }, colorPickerTable = new Table()); } }; } @Override public String getTitle() { return "Animations"; } private Listener onExpandCollapse(final Table content, final String tag) { return new Listener() { @Override public void invoke() { if (expandedTags.contains(tag)) { expandedTags.remove(tag); } else { expandedTags.add(tag); } updateAnimationList(content); } }; } @Override public void populate(Table content) { addAnimationList(content); content.add(currentAnimTable = new Table() { { add(new VisLabel("Select an animation from the left", Color.RED)); } }).expand().fill(); } @Override public void save() { ObjectMap<String, AnimationDefinition> newObjects = new ObjectMap<>(); if (rootDefinition.animations == null) { return; } ObjectMap<String, AnimationDefinition> oldObjects = rootDefinition.animations; for (AnimationDefinition item : oldObjects.values()) { newObjects.put(item.getName(), item); } rootDefinition.animations = newObjects; Json json = new Json(); json.setOutputType(OutputType.json); String jsonStr = json.prettyPrint(rootDefinition); Gdx.files.external("umbracraft/" + getTitle().toLowerCase() + ".json").writeString(jsonStr, false); } private void updateAnimationList(final Table content) { animationListContents.clear(); animationListContents.add(new Table() { { defaults().expandX().uniformX().fill(); if (rootDefinition != null && rootDefinition.animations != null) { // add all definitions to a new list sortedDefinitions.clear(); for (final AnimationDefinition definition : rootDefinition.animations.values()) { sortedDefinitions.add(definition); } // sort the list sortedDefinitions.sort(); // now display the list String heading = null; for (final AnimationDefinition definition : sortedDefinitions) { if (definition.tag != null && !definition.tag.equals(heading)) { heading = definition.tag; add(new Table() { { add(new VisLabel(definition.tag)).expandX(); add(WidgetUtils.button(expandedTags.contains(definition.tag) ? "-" : "+", onExpandCollapse(content, definition.tag))).size(15); } }).expandX().width(200).row(); } if (expandedTags.contains(definition.tag)) { final VisTextButton animButton = new VisTextButton(definition.name != null ? definition.name : "New Animation"); animButton.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { currentAnimTable.clear(); createCurrentAnimTable(content, currentAnimTable, definition, animButton); highlightedButton = definition.name; updateHighlighted(); } }); add(animButton).row(); buttons.add(animButton); } } } add().expandY().fill().row(); VisTextButton addButton = new VisTextButton("Add Animation"); addButton.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { rootDefinition.add(); content.clear(); populate(content); } }); add(addButton).padTop(20).padBottom(20).row(); } }).expandY().fillY(); updateHighlighted(); } /** The listener for updating anything when the {@link AnimationDefinition} * changes */ private TypeListener<String> updateButtonListener(final Image image, final VisTextButton button, final Table scroll, final AnimationDefinition definition) { return new TypeListener<String>() { @Override public void invoke(String type) { if (definition.filename != null && definition.filename.length() > 0) { String path = "sprites/animations/" + definition.filename + ".png"; String extPath = Editor.db().config().projectPath + Editor.db().config().spritePath + definition.filename + ".png"; if (Gdx.files.internal(path).exists()) { image.setVisible(true); image.setDrawable(new TextureRegionDrawable(new TextureRegion(new Texture(Gdx.files.internal(path))))); } else if (Gdx.files.absolute(extPath).exists()) { image.setVisible(true); image.setDrawable(new TextureRegionDrawable(new TextureRegion(new Texture(Gdx.files.absolute(extPath))))); } else { image.setVisible(false); } } scroll.clear(); scroll.add(createFrames(scroll, definition)); updatePreviewAnimation(definition); button.setText(definition.name); } }; } private void updateHighlighted() { if (highlightedButton == null) { return; } // highlight for (VisTextButton a : buttons) { String text = a.getLabel().getText().toString(); a.getLabel().setColor(highlightedButton.equals(text) ? Color.YELLOW : Color.WHITE); } } private void updatePreviewAnimation(AnimationDefinition definition) { if (definition == null || previewTable == null) { return; } previewTable.clear(); previewTable.add(new AnimationPreview(definition)).size(definition.width * 2, definition.height * 2).pad(30); } }