/*******************************************************************************
* Rhythos Editor is a game editor and project management tool for making RPGs on top of the Rhythos Game system.
*
* Copyright (C) 2013 David Maletz
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package mrpg.editor.resource;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.DefaultListModel;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import mrpg.editor.DragList;
import mrpg.editor.Filter;
import mrpg.editor.MapEditor;
import mrpg.editor.ResourceChooser;
import mrpg.editor.TilesetViewer;
import mrpg.editor.WorkspaceBrowser;
public class AnimationSet extends TypedResource implements Iterable<Animation> {
private static final long serialVersionUID = -5394199071824545816L;
public static final String EXT = "ani", TYPE = "an"; private static final short VERSION=1;
private static final Icon icon = MapEditor.getIcon("chr_appearance");
private ImageResource image; private BufferedImage cache; private int width, height;
private final ArrayList<Animation> animations = new ArrayList<Animation>();
public AnimationSet(File f, MapEditor editor){super(f, editor); width = 4; height = 4;}
public Icon getIcon(){return icon;}
public int numAnimations(){return animations.size();}
public Iterator<Animation> iterator(){return animations.iterator();}
public Animation getAnimation(int i){return animations.get(i);}
public int getWidth(){return width;}
public int getHeight(){return height;}
public Icon getAnimatedIcon(JLabel l, int i, BufferedImage img){
return new Animation.Icon(l, animations.get(i), img, width, height);
}
public int getHeaderSize(){return super.getHeaderSize()+ImageResource.getSize(image);}
public void writeInner(DataOutputStream out) throws Exception {
ImageResource.write(out, image); out.writeShort(width);
out.writeShort(height); out.writeShort(animations.size()); for(Animation a : animations) a.write(out);
}
public void readInner(DataInputStream in) throws Exception {
Project p = WorkspaceBrowser.getProject(this); image = ImageResource.read(in, p);
width = in.readShort(); height = in.readShort(); short nAni = in.readShort();
animations.clear(); for(int i=0; i<nAni; i++) animations.add(Animation.read(p, in, this));
}
protected void read(File f) throws Exception {MapEditor.deferRead(this, MapEditor.DEF_TILEMAP);}
public String getType(){return TYPE;}
public short getVersion(){return VERSION;}
public JDialog getProperties(){return new Properties(this);}
public static AnimationSet create(Resource parent, MapEditor e, Project p) throws Exception {
String dir = parent.getFile().toString();
File f = new File(dir,"New Animation"+"."+EXT);
AnimationSet ret = new AnimationSet(f,e); ret.newId(p); ret._setName(null); ret.properties();
if(!((Properties)ret.properties).updated) throw new Exception();
ret.addToProject(p,false); return ret;
}
private static class Properties extends TypedResource.Properties implements MouseListener, ListSelectionListener, ChangeListener, DragList.Listener {
private static final long serialVersionUID = -4987880557990107307L;
private static final String ADD = "add", REM = "rem";
private JSpinner width, height; private JLabel image_thumb; private JList animations; private ImageResource image;
public Properties(AnimationSet ani){super(ani, "Animation Set Properties");}
public void addControls(JPanel settings){
AnimationSet ani = (AnimationSet)resource;
JPanel inner = new JPanel(); inner.setBorder(BorderFactory.createTitledBorder("Dimensions"));
width = new JSpinner(new SpinnerNumberModel(ani.width, 1, Short.MAX_VALUE, 1)); width.addChangeListener(this);
inner.add(width); inner.add(new JLabel(" X ")); height = new JSpinner(new SpinnerNumberModel(ani.height, 1, Short.MAX_VALUE, 1));
height.addChangeListener(this); inner.add(height);
settings.add(inner); inner = new JPanel(new BorderLayout()); JPanel p = new JPanel(new BorderLayout());
p.setBorder(BorderFactory.createTitledBorder("Animations"));
animations = new JList(); animations.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
animations.addMouseListener(this); animations.addListSelectionListener(this); new DragList(animations, Animation.class, this);
JScrollPane pane = new JScrollPane(animations); pane.setPreferredSize(new Dimension(150,120));
p.add(pane, BorderLayout.CENTER); JButton add = new JButton("+"); add.setActionCommand(ADD);
add.addActionListener(this); JButton rem = new JButton("-"); rem.setActionCommand(REM);
rem.addActionListener(this); JPanel inner2 = new JPanel(new GridLayout(1,2)); inner2.add(add);
inner2.add(rem); p.add(inner2, BorderLayout.SOUTH); inner.add(p, BorderLayout.WEST);
image_thumb = new JLabel(new ImageIcon());
JPanel panel = new JPanel(new BorderLayout()); panel.setBorder(BorderFactory.createTitledBorder("Preview"));
pane = new JScrollPane(image_thumb); pane.setPreferredSize(ImageResource.THUMB_SIZE);
pane.setBorder(BorderFactory.createLoweredBevelBorder()); panel.add(pane, BorderLayout.CENTER); inner2 = new JPanel();
JButton set = new JButton("Set"); set.setActionCommand(MapEditor.SET); set.addActionListener(this); inner2.add(set);
JButton clear = new JButton("Clear"); clear.setActionCommand(MapEditor.CLEAR); clear.addActionListener(this); inner2.add(clear);
panel.add(inner2, BorderLayout.SOUTH); inner.add(panel, BorderLayout.CENTER); settings.add(inner);
}
public void updateControls(){
AnimationSet ani = (AnimationSet)resource; image = ani.image;
width.setValue(ani.width); height.setValue(ani.height); DefaultListModel m = new DefaultListModel();
for(Animation i : ani.animations) m.addElement(i); animations.setModel(m);
updateCache(); if(ani.animations.size() > 0) animations.setSelectedIndex(0);
}
public void acceptControls(){
AnimationSet ani = (AnimationSet)resource;
ani.image = image; ani.animations.clear(); ListModel m = animations.getModel();
for(int i=0; i<m.getSize(); i++) ani.animations.add((Animation)m.getElementAt(i));
ani.width = (Integer)width.getValue(); ani.height = (Integer)height.getValue();
}
public boolean saveOnEdit(){return true;}
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
if(command == REM){
int i = animations.getSelectedIndex(); if(i != -1) ((DefaultListModel)animations.getModel()).removeElementAt(i);
} else if(command == ADD){
Animation ani = editAnimation(null); if(ani != null){
DefaultListModel m = (DefaultListModel)animations.getModel(); int sz = m.getSize();
m.addElement(ani); animations.setSelectedIndex(sz);
}
} else if(command == MapEditor.SET){
Project p = resource.getProject();
if(p == null){JOptionPane.showMessageDialog(this, "Animation Set is not added to any project, no images to load...", "Cannot Find Images", JOptionPane.ERROR_MESSAGE); return;}
ImageResource im = ImageResource.choose(p, image);
if(im != null){image = im; updateCache();}
} else if(command == MapEditor.CLEAR){
image = null; updateCache();
} else super.actionPerformed(e);
}
public void mouseClicked(MouseEvent e){
if(e.getClickCount() == 2){
int idx = ((JList)e.getSource()).getSelectedIndex(); if(idx == -1) return;
Object o = ((JList)e.getSource()).getSelectedValue();
Animation ani = editAnimation((Animation)o); if(ani != null){
DefaultListModel m = (DefaultListModel)animations.getModel();
m.setElementAt(ani, idx); updatePreview();
}
}
}
public void mouseEntered(MouseEvent e){}
public void mouseExited(MouseEvent e){}
public void mousePressed(MouseEvent e){}
public void mouseReleased(MouseEvent e){}
public void valueChanged(ListSelectionEvent e){
if(!e.getValueIsAdjusting()) updatePreview();
}
public void stateChanged(ChangeEvent e){updateCache();}
private static final Color trans_black = new Color(0x88000000,true), trans_white = new Color(0xAAffffff,true);
private void updateCache(){
int w = (Integer)width.getValue(), h = (Integer)height.getValue(); BufferedImage b;
if(image == null){
b = new BufferedImage(TilesetViewer.TILE_SIZE*w, TilesetViewer.TILE_SIZE*h, BufferedImage.TYPE_INT_ARGB);
Graphics g = b.getGraphics(); g.setColor(Color.white); g.fillRect(0, 0, b.getWidth(), b.getHeight());
} else {
BufferedImage im = image.getImage(); ColorModel cm = im.getColorModel();
b = new BufferedImage(cm, im.copyData(null), cm.isAlphaPremultiplied(), null);
} int iw = b.getWidth()/w, ih = b.getHeight()/h; Graphics g = b.getGraphics();
g.setFont(g.getFont().deriveFont(Font.BOLD, 16)); g.setColor(trans_black);
for(int y=0; y<h; y++)
for(int x=0; x<w; x++){
g.drawRect(x*iw, y*ih, iw-1, ih-1); String i = Integer.toString(y*w+x+1); FontMetrics f = g.getFontMetrics();
int sw = f.stringWidth(i), sx = x*iw+((iw-sw)>>1), sy = y*ih+ih-20;
g.setColor(trans_white); g.fillRect(sx-2, sy, sw+4, 18);
g.setColor(trans_black); g.drawString(i, sx, sy+16);
}
((AnimationSet)resource).cache = b; updatePreview();
}
private void updatePreview(){
AnimationSet ani = (AnimationSet)resource;
Animation a = (Animation)animations.getSelectedValue();
if(a == null || a.numFrames() == 0) image_thumb.setIcon(new ImageIcon(ani.cache));
else image_thumb.setIcon(new Animation.Icon(image_thumb, a, ani.cache, (Integer)width.getValue(), (Integer)height.getValue()));
}
private AnimationEdit ani_edit;
private Animation editAnimation(Animation i){
if(ani_edit == null) ani_edit = new AnimationEdit(this, ((AnimationSet)resource));
ani_edit.show(i, (Integer)width.getValue(), (Integer)height.getValue()); if(ani_edit.updated) return ani_edit.get(); else return null;
}
public void updateDrag(){}
public Object paste(Object o){
Animation clipboard = (Animation)o; String name = clipboard.getName(); int copy = 0; boolean changed = true;
while(changed){
changed = false; ListModel m = animations.getModel(); for(int i=0; i<m.getSize(); i++){
Animation a = (Animation)m.getElementAt(i);
if(a.getName().equals(name) && a.getDir() == clipboard.getDir()){
copy++; name = clipboard.getName()+" copy"+((copy==1)?"":" "+copy); changed = true; break;
}
}
} return new Animation(name, (byte)clipboard.getDir(), clipboard.speed, ((AnimationSet)resource), clipboard.getFramesList());
}
}
private static class AnimationEdit extends JDialog implements ActionListener {
private static final long serialVersionUID = -4987880557990107307L;
public boolean updated; private final JTextField frames; private Animation cur; private ButtonGroup speed;
private final JComboBox name, dir; private final FrameSelector image_thumb; private AnimationSet animation;
public AnimationEdit(JDialog d, AnimationSet ani){
super(JOptionPane.getFrameForComponent(d), "Animation", true); animation = ani; setResizable(true);
Container c = getContentPane(); c.setLayout(new BoxLayout(c, BoxLayout.Y_AXIS)); JPanel settings = new JPanel();
settings.setLayout(new BoxLayout(settings, BoxLayout.Y_AXIS)); settings.setBorder(BorderFactory.createRaisedBevelBorder());
JPanel inner = new JPanel(); inner.setBorder(BorderFactory.createTitledBorder("Name"));
name = new JComboBox(Animation.default_ani); name.setEditable(true); JTextField t = (JTextField)name.getEditor().getEditorComponent();
t.setActionCommand(MapEditor.OK); t.addActionListener(this); inner.add(name);
dir = new JComboBox(Animation.dirs); dir.setEditable(false); inner.add(dir); settings.add(inner);
inner = new JPanel(new BorderLayout()); inner.setBorder(BorderFactory.createTitledBorder("Select Frames"));
image_thumb = new FrameSelector(this, new ImageIcon());
JScrollPane pane = new JScrollPane(image_thumb); pane.setPreferredSize(new Dimension(350,350));
pane.setBorder(BorderFactory.createLoweredBevelBorder()); inner.add(pane, BorderLayout.CENTER);
settings.add(inner); inner = new JPanel(new BorderLayout()); inner.setBorder(BorderFactory.createTitledBorder("Frames"));
frames = new JTextField(); inner.add(frames, BorderLayout.CENTER); settings.add(inner);
speed = new ButtonGroup(); inner = new JPanel(); inner.setBorder(BorderFactory.createTitledBorder("Animation Speed"));
JRadioButton r = new JRadioButton("Slowest"); r.setActionCommand("5"); speed.add(r); inner.add(r);
r = new JRadioButton("Slower"); r.setActionCommand("4"); speed.add(r); inner.add(r);
r = new JRadioButton("Slow"); r.setActionCommand("3"); speed.add(r); inner.add(r);
r = new JRadioButton("Normal"); r.setSelected(true); r.setActionCommand("2"); speed.add(r); inner.add(r);
r = new JRadioButton("Fast"); r.setActionCommand("1"); speed.add(r); inner.add(r); settings.add(inner);
c.add(settings);
inner = new JPanel();
JButton b = new JButton("Ok"); b.setActionCommand(MapEditor.OK); b.addActionListener(this); inner.add(b);
b = new JButton("Cancel"); b.setActionCommand(MapEditor.CANCEL); b.addActionListener(this); inner.add(b);
c.add(inner);
pack();
}
public void show(Animation i, int w, int h){
updated = false; cur = i; if(i != null){name.setSelectedItem(i.getName()); dir.setSelectedIndex(i.getDir()); frames.setText(i.getFrames());}
else {name.setSelectedItem(Animation.default_ani[0]); dir.setSelectedIndex(0); frames.setText("");}
JTextField t = (JTextField)name.getEditor().getEditorComponent(); t.requestFocus(); t.selectAll();
if(i != null){
Enumeration<AbstractButton> e = speed.getElements();
String sp = Byte.toString(i.speed); while(e.hasMoreElements()){
AbstractButton r = e.nextElement(); if(r.getActionCommand().equals(sp)) r.setSelected(true);
}
} image_thumb.set(new ImageIcon(animation.cache),w,h); setVisible(true);
}
public Animation get(){
return new Animation(name.getSelectedItem().toString(), (byte)dir.getSelectedIndex(),
Byte.parseByte(speed.getSelection().getActionCommand()), animation, frames.getText());
}
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
if(command == MapEditor.OK){
ListModel m = ((Properties)animation.properties).animations.getModel(); for(int i=0; i<m.getSize(); i++){
Animation a = (Animation)m.getElementAt(i);
if(cur != a && a.getName().equals(name.getSelectedItem().toString()) && a.getDir() == dir.getSelectedIndex()){
JOptionPane.showMessageDialog(this, "The animation "+a+" already exists!", "Unable to Add Animation", JOptionPane.ERROR_MESSAGE); return;
}
} updated = true; setVisible(false);
} else setVisible(false);
}
}
private static class FrameSelector extends JLabel implements MouseListener, MouseMotionListener {
private static final long serialVersionUID = -9201011643596250228L;
private int lastFrame, w, h, iw, ih; private AnimationEdit edit;
public FrameSelector(AnimationEdit e, Icon i){
super(i); setHorizontalAlignment(JLabel.LEFT); setVerticalAlignment(JLabel.TOP);
addMouseListener(this); addMouseMotionListener(this); edit = e;
}
public void mouseClicked(MouseEvent e){}
public void mouseEntered(MouseEvent e){}
public void mouseExited(MouseEvent e){}
public void mousePressed(MouseEvent e){mouseDragged(e);}
public void mouseReleased(MouseEvent e){lastFrame = -1;}
public void mouseDragged(MouseEvent e){
if(getIcon() == null) return; int x = e.getX()/iw, y = e.getY()/ih, f = y*w+x;
if(x < 0 || x >= w || y < 0 || y >= h || f == lastFrame) return; lastFrame = f;
String t = edit.frames.getText(); int len = t.length(); t = t.trim(); StringBuilder b = new StringBuilder();
if(t.length() > 0 && !t.endsWith(",")) b.append(","); b.append(f+1);
try{edit.frames.getDocument().insertString(len, b.toString(), null);}catch(Exception ex){ex.printStackTrace();}
}
public void mouseMoved(MouseEvent e){}
public void set(Icon i, int _w, int _h){
lastFrame = -1; setIcon(i); w = _w; h = _h; iw = i.getIconWidth()/w; ih = i.getIconHeight()/h;
}
}
public String getExt(){return EXT;}
public static void register() throws Exception {
Resource.register("Animation Files", AnimationSet.EXT, AnimationSet.TYPE, AnimationSet.class);
Folder.new_options.addMenu("Sprite", "chr_appearance").
addItem("Animation Set", "chr_appearance", new CreateAnimationAction());
}
private static class CreateAnimationAction implements ActionListener {
public void actionPerformed(ActionEvent e){
MapEditor.instance.getBrowser().addResource(AnimationSet.class);
}
}
public static AnimationSet choose(Resource root, Resource selected){
ResourceChooser c = new ResourceChooser(root, selected, FILTER);
c.setVisible(true); return (AnimationSet)c.getSelectedResource();
}
private static class AFilter extends JPanel implements Filter {
private static final long serialVersionUID = 907354882348925575L;
private DefaultListModel model;
public AFilter(){
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); model = new DefaultListModel(); JList list = new JList(model);
list.setEnabled(false); JScrollPane pane = new JScrollPane(list); pane.setPreferredSize(new Dimension(150,120));
pane.setBorder(BorderFactory.createLoweredBevelBorder()); add(new JLabel("Animation List:")); add(pane);
}
public boolean filter(Resource r){String ext = r.getExt(); return ext == null || ext == EXT;}
private void reset(){model.clear();}
public JPanel getPreview(){reset(); return this;}
public boolean showPreview(Resource r){
reset(); if(r.getExt() == null) return false;
for(Animation a : ((AnimationSet)r).animations) model.addElement(a);
return true;
}
} public static final Filter FILTER = new AFilter();
}