/*******************************************************************************
* 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.Dimension;
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.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.util.ArrayList;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.DefaultComboBoxModel;
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.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
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.WorkspaceBrowser;
public class Sprite extends TypedResource {
private static final long serialVersionUID = -5394199071824545816L;
public static final String EXT = "spr", TYPE = "sp"; private static final short VERSION=1;
private static final Icon icon = MapEditor.getIcon("chr_appearance");
private final ArrayList<Layer> layers = new ArrayList<Layer>(); private AnimationSet animation;
public Sprite(File f, MapEditor editor){super(f, editor);}
public String getType(){return TYPE;}
public short getVersion(){return VERSION;}
public JDialog getProperties(){return new Properties(this);}
public Icon getIcon(){return icon;}
public void addToProject(Project p, boolean changeProject) throws Exception {
super.addToProject(p, changeProject);
if(changeProject){
if(animation != null)
try{animation = (AnimationSet)p.getById(AnimationSet.TYPE, animation.getId());}catch(Exception ex){animation = null;}
for(Layer l : layers){
if(l != null && l.image != null){
try{l.image = (ImageResource)p.getById(l.image.getType(), l.image.getId());}catch(Exception ex){l.image = null;}
} if(l != null && l.layer != null){
try{l.layer = (SpriteLayer)p.getById(SpriteLayer.TYPE, l.layer.getId());}catch(Exception ex){l.layer = null;}
}
}
}
}
public void writeInner(DataOutputStream out) throws Exception {
out.writeShort(layers.size()); for(Layer l : layers) l.write(out);
out.writeLong((animation==null)?0:animation.getId());
}
public void readInner(DataInputStream in) throws Exception {
Project p = WorkspaceBrowser.getProject(this);
short nLayers = in.readShort();
layers.clear(); for(int i=0; i<nLayers; i++) layers.add(Layer.read(p, in));
long aid = in.readLong(); AnimationSet ani = null;
if(aid != 0) try{ani = (AnimationSet)p.getById(AnimationSet.TYPE, aid);}catch(Exception ex){}
animation = ani;
}
protected void read(File f) throws Exception {MapEditor.deferRead(this, MapEditor.DEF_TILEMAP);}
public static Sprite create(Resource parent, MapEditor e, Project p) throws Exception {
String dir = parent.getFile().toString();
File f = new File(dir,"New Sprite"+"."+EXT);
Sprite ret = new Sprite(f,e); ret._setName(null); ret.newId(p); 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, DragList.Listener {
private static final long serialVersionUID = -4987880557990107307L;
private static final String ADD = "add", REM = "rem";
private AnimationSet animation; private JTextField ani_label;
private JLabel image_thumb; private JList layers; private JComboBox preview_ani;
public Properties(Sprite chr){super(chr, "Sprite Properties");}
public void addControls(JPanel settings){
JPanel inner = new JPanel(new BorderLayout()); JPanel p = new JPanel(new BorderLayout());
p.setBorder(BorderFactory.createTitledBorder("Layers"));
layers = new JList(); layers.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
layers.addMouseListener(this); new DragList(layers, Layer.class, this);
JScrollPane pane = new JScrollPane(layers); pane.setPreferredSize(new Dimension(200,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());
pane = new JScrollPane(image_thumb); pane.setPreferredSize(ImageResource.THUMB_SIZE);
pane.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("Preview"),
BorderFactory.createLoweredBevelBorder())); inner.add(pane, BorderLayout.CENTER); settings.add(inner);
inner = new JPanel(); inner.setBorder(BorderFactory.createTitledBorder("Animation")); inner.setLayout(new BoxLayout(inner, BoxLayout.Y_AXIS));
p = new JPanel(); ani_label = new JTextField("", 15); ani_label.setEditable(false); p.add(ani_label);
JButton set = new JButton("Set"); set.setActionCommand(MapEditor.SET); set.addActionListener(this); p.add(set);
JButton clear = new JButton("Clear"); clear.setActionCommand(MapEditor.CLEAR); clear.addActionListener(this); p.add(clear);
inner.add(p); p = new JPanel(); p.add(new JLabel("Preview: ")); preview_ani = new JComboBox(); preview_ani.setEnabled(false);
preview_ani.addActionListener(this); preview_ani.setPreferredSize(ani_label.getPreferredSize()); p.add(preview_ani); inner.add(p);
settings.add(inner);
}
public void updateControls(){
Sprite chara = (Sprite)resource; setAnimation(chara.animation); DefaultListModel m = new DefaultListModel();
for(Layer i : chara.layers) m.addElement(i); layers.setModel(m);
if(chara.layers.size() > 0) layers.setSelectedIndex(0); updatePreview();
}
public void acceptControls(){
Sprite chara = (Sprite)resource; chara.animation = animation; chara.layers.clear();
ListModel m = layers.getModel(); for(int i=0; i<m.getSize(); i++) chara.layers.add((Layer)m.getElementAt(i));
}
public boolean saveOnEdit(){return true;}
public void actionPerformed(ActionEvent e) {
if(e.getSource() == preview_ani){
if(preview_ani.isEnabled()) updateAnimation(); return;
}
String command = e.getActionCommand();
if(command == REM){
int i = layers.getSelectedIndex(); if(i != -1){((DefaultListModel)layers.getModel()).removeElementAt(i); updatePreview();}
} else if(command == ADD){
Layer layer = editLayer(null); if(layer != null){
DefaultListModel m = (DefaultListModel)layers.getModel(); int sz = m.getSize();
m.addElement(layer); layers.setSelectedIndex(sz); updatePreview();
}
} else if(command == MapEditor.SET){
AnimationSet a = AnimationSet.choose(getProject(), animation); if(a != null) setAnimation(a);
} else if(command == MapEditor.CLEAR){
setAnimation(null);
} else super.actionPerformed(e);
}
private void setAnimation(AnimationSet a){
animation = a; if(animation != null && animation.numAnimations() > 0){
ani_label.setText(animation.getName()); preview_ani.setEnabled(false); DefaultComboBoxModel model = (DefaultComboBoxModel)preview_ani.getModel();
model.removeAllElements(); for(Animation ani : animation) model.addElement(ani); preview_ani.setSelectedIndex(0); preview_ani.setEnabled(true);
} else {ani_label.setText(""); preview_ani.setEnabled(false); ((DefaultComboBoxModel)preview_ani.getModel()).removeAllElements();}
updateAnimation();
}
public void updateDrag(){updatePreview();}
public Object paste(Object o){return o;}
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();
Layer layer = editLayer((Layer)o); if(layer != null){
DefaultListModel m = (DefaultListModel)layers.getModel();
m.setElementAt(layer, idx); updatePreview();
}
}
}
public void mouseEntered(MouseEvent e){}
public void mouseExited(MouseEvent e){}
public void mousePressed(MouseEvent e){}
public void mouseReleased(MouseEvent e){}
private BufferedImage cache;
private void updatePreview(){
BufferedImage b = null; ListModel model = layers.getModel(); int sz = model.getSize();
for(int i=0; i<sz; i++){
Layer l = (Layer)model.getElementAt(i); if(l == null) continue;
BufferedImage im = l.getImage(); if(im == null) continue;
if(b == null) b = im; else{
int w = im.getWidth(), h = im.getHeight(); if(b.getWidth() < w || b.getHeight() < h){
BufferedImage tmp = new BufferedImage(Math.max(b.getWidth(),w),Math.max(b.getHeight(),h),BufferedImage.TYPE_INT_ARGB);
tmp.setData(b.getData()); b = tmp;
} b.getGraphics().drawImage(im, 0, 0, null);
}
} cache = b; updateAnimation();
}
private void updateAnimation(){
if(cache != null){
if(!preview_ani.isEnabled() || preview_ani.getSelectedIndex() == -1) image_thumb.setIcon(new ImageIcon(cache));
else image_thumb.setIcon(animation.getAnimatedIcon(image_thumb, preview_ani.getSelectedIndex(), cache));
} else image_thumb.setIcon(new ImageIcon());
}
private Project getProject(){
Project p = resource.getProject();
if(p == null){JOptionPane.showMessageDialog(this, "Sprite is not added to any project, no resources to load...", "Cannot Find Resources", JOptionPane.ERROR_MESSAGE); return null;}
return p;
}
public Layer editLayer(Layer l){
Project p = getProject();
return (l==null)?chooseLayer(p, null, 0, 0):chooseLayer(p, (l.layer==null)?l.image:l.layer, l.image_id, l.color);
}
}
private static class Layer {
private SpriteLayer layer; private ImageResource image; private int image_id, color;
public Layer(ImageResource l){image = l; layer = null;}
public Layer(SpriteLayer l, int i, int c){layer = l; image = null; image_id = i; color = c;}
public void write(DataOutputStream out) throws Exception {
if(layer == null){
out.writeShort(-1); ImageResource.write(out, image);
} else {
out.writeShort(image_id); out.writeShort(color); out.writeLong(layer.getId());
}
}
public static Layer read(Project p, DataInputStream in) throws Exception {
short image_id = in.readShort(); if(image_id < 0){
return new Layer(ImageResource.read(in, p));
} else {
short color = in.readShort(); SpriteLayer layer = null;
try{layer = (SpriteLayer)p.getById(SpriteLayer.TYPE, in.readLong());}catch(Exception ex){}
return new Layer(layer, image_id, color);
}
}
public BufferedImage getImage(){
if(layer == null){
if(image == null) return null; else{
BufferedImage im = image.getImage(); ColorModel cm = im.getColorModel();
return new BufferedImage(cm, im.copyData(null), cm.isAlphaPremultiplied(), null);
}
} else return layer.get(image_id, color);
}
public String toString(){
if(layer == null) return (image == null)?"Empty":image.getName();
else return layer.getName()+" ("+layer.getColorName(color)+" "+layer.getImageName(image_id)+")";
}
}
public String getExt(){return EXT;}
public static void register() throws Exception {
Resource.register("Sprite Files", Sprite.EXT, Sprite.TYPE, Sprite.class);
Folder.new_options.addMenu("Sprite", "chr_appearance").
addItem("Sprite", "chr_appearance", new CreateCharaAction());
}
private static class CreateCharaAction implements ActionListener {
public void actionPerformed(ActionEvent e){
MapEditor.instance.getBrowser().addResource(Sprite.class);
}
}
private static Layer chooseLayer(Resource root, Resource selected, int image, int color){
ResourceChooser c = new ResourceChooser(root, selected, FILTER); if(selected != null) FILTER.set(image, color);
c.setVisible(true); Resource r = c.getSelectedResource(); if(r == null) return null;
if(ImageResource.isImage(r)) return new Layer((ImageResource)r); else return new Layer((SpriteLayer)r, FILTER.images.getSelectedIndex(), FILTER.colors.getSelectedIndex());
}
private static class LFilter extends JPanel implements Filter, ListSelectionListener {
private static final long serialVersionUID = 907354882348925575L;
private JLabel image_thumb; private JList images, colors; private SpriteLayer layer;
private JPanel createLabeled(JScrollPane p, String label){
JPanel ret = new JPanel(new BorderLayout()); ret.add(new JLabel(label), BorderLayout.NORTH); ret.add(p, BorderLayout.CENTER); return ret;
}
public LFilter(){
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); image_thumb = new JLabel(new ImageIcon());
JScrollPane pane = new JScrollPane(image_thumb); pane.setPreferredSize(ImageResource.THUMB_SIZE);
pane.setBorder(BorderFactory.createLoweredBevelBorder()); add(pane); JPanel p = new JPanel(new GridLayout(1,2));
images = new JList(new DefaultListModel()); images.setEnabled(false); images.addListSelectionListener(this);
pane = new JScrollPane(images); pane.setPreferredSize(new Dimension(50,100)); p.add(createLabeled(pane, "Images"));
colors = new JList(new DefaultListModel()); colors.setEnabled(false); colors.addListSelectionListener(this);
pane = new JScrollPane(colors); pane.setPreferredSize(new Dimension(50,100)); p.add(createLabeled(pane, "Colors")); add(p);
}
public boolean filter(Resource r){String ext = r.getExt(); return ext == null || ext == SpriteLayer.EXT || ImageResource.isImage(r);}
public void set(int image, int color){
if(images.isEnabled()) images.setSelectedIndex(image); if(colors.isEnabled()) colors.setSelectedIndex(color);
}
private void reset(BufferedImage im){
image_thumb.setIcon((im==null)?new ImageIcon():new ImageIcon(im)); images.setEnabled(false); colors.setEnabled(false);
((DefaultListModel)images.getModel()).clear(); ((DefaultListModel)colors.getModel()).clear();
}
public JPanel getPreview(){reset(null); return this;}
public boolean showPreview(Resource r){
String ext = r.getExt(); if(ext == null){reset(null); return false;}
else if(ImageResource.isImage(r)){reset(((ImageResource)r).getImage()); return true;}
layer = (SpriteLayer)r; reset(layer.get(0, 0)); DefaultListModel m = (DefaultListModel)images.getModel();
int sz = layer.getImageCount(); for(int i=0; i<sz; i++) m.addElement(layer.getImageName(i)); m = (DefaultListModel)colors.getModel();
sz = layer.getColorCount(); for(int i=0; i<sz; i++) m.addElement(layer.getColorName(i));
images.setSelectedIndex(0); colors.setSelectedIndex(0); images.setEnabled(true); colors.setEnabled(true);
return true;
}
public void valueChanged(ListSelectionEvent e) {
JList l = (JList)e.getSource(); if(e.getValueIsAdjusting() || !l.isEnabled()) return;
BufferedImage im = layer.get(images.getSelectedIndex(), colors.getSelectedIndex());
image_thumb.setIcon((im==null)?new ImageIcon():new ImageIcon(im));
}
} private static final LFilter FILTER = new LFilter();
}