diff --git a/.gitignore b/.gitignore index f89d9798a..1c631ef38 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ build mingw-bin -paintown +/paintown .scon* .tmp peg_*.py diff --git a/editor/src/main/java/com/rafkind/paintown/CloseHook.java b/editor/src/main/java/com/rafkind/paintown/CloseHook.java new file mode 100644 index 000000000..e6d47b236 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/CloseHook.java @@ -0,0 +1,38 @@ +package com.rafkind.paintown; + +import java.awt.event.WindowListener; +import java.awt.event.WindowEvent; + +public class CloseHook implements WindowListener { + + private Lambda0 function; + + public CloseHook( Lambda0 _function ){ + function = _function; + } + + public void windowActivated( WindowEvent e ){ + } + + public void windowClosed( WindowEvent e ){ + } + + public void windowClosing( WindowEvent e ){ + try{ + this.function.invoke(); + } catch (Exception ex){ + } + } + + public void windowDeactivated( WindowEvent e ){ + } + + public void windowDeiconified( WindowEvent e ){ + } + + public void windowIconified( WindowEvent e ){ + } + + public void windowOpened( WindowEvent e ){ + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/Closer.java b/editor/src/main/java/com/rafkind/paintown/Closer.java new file mode 100644 index 000000000..dbe075913 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/Closer.java @@ -0,0 +1,16 @@ +package com.rafkind.paintown; + +public abstract class Closer{ + private static int opened = 0; + + public static void open(){ + opened += 1; + } + + public static void close(){ + opened -= 1; + if (opened <= 0){ + System.exit(0); + } + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/Config.java b/editor/src/main/java/com/rafkind/paintown/Config.java new file mode 100644 index 000000000..b9f087cb4 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/Config.java @@ -0,0 +1,68 @@ +package com.rafkind.paintown; + +import java.util.HashMap; +import java.io.*; + +/* saves arbitrary data to a file so it can be loaded in between program instances */ +public class Config{ + private HashMap config = new HashMap(); + private static Object lock = new Object(); + private static Config instance = null; + private static final String savedFile = "editor-config.obj"; + + private Config(){ + config = loadData(); + } + + private void saveData(){ + try{ + ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream(savedFile)); + try{ + output.writeObject(config); + } finally { + output.close(); + } + } catch (IOException fail){ + System.err.println(fail); + } + } + + private HashMap loadData(){ + try{ + ObjectInputStream input = new ObjectInputStream(new FileInputStream(savedFile)); + try{ + Object in = input.readObject(); + if (in instanceof HashMap){ + return (HashMap) in; + } + return new HashMap(); + } finally { + input.close(); + } + } catch (ClassNotFoundException fail){ + return new HashMap(); + } catch (IOException fail){ + System.err.println(fail); + return new HashMap(); + } + } + + public static Config getConfig(){ + synchronized (lock){ + if (instance == null){ + instance = new Config(); + } + + return instance; + } + } + + public synchronized Serializable get(String name){ + return config.get(name); + } + + public synchronized void set(String name, Serializable what){ + config.put(name, what); + saveData(); + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/Data.java b/editor/src/main/java/com/rafkind/paintown/Data.java new file mode 100644 index 000000000..cfe7f84d9 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/Data.java @@ -0,0 +1,18 @@ +package com.rafkind.paintown; + +import java.io.File; + +public class Data{ + private static File dataPath = new File( "data" ); + + private Data(){ + } + + public static File getDataPath(){ + return dataPath; + } + + public static void setDataPath(File file){ + dataPath = file; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/DirectoryModel.java b/editor/src/main/java/com/rafkind/paintown/DirectoryModel.java new file mode 100644 index 000000000..272e5ea6d --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/DirectoryModel.java @@ -0,0 +1,60 @@ +package com.rafkind.paintown; + +import java.io.File; +import java.util.Iterator; +import java.util.List; +import java.util.ArrayList; + +import javax.swing.ListModel; +import javax.swing.event.ListDataListener; +import javax.swing.event.ListDataEvent; + +public class DirectoryModel implements ListModel { + + private List files; + private List listeners; + + public DirectoryModel( String dir ){ + listeners = new ArrayList(); + files = new ArrayList(); + setDirectory( dir ); + } + + public void setDirectory( File dir ){ + if ( dir.isDirectory() ){ + files.clear(); + files.add( new File( dir.getPath() + "/.." ) ); + File[] fs = dir.listFiles(); + for ( int i = 0; i < fs.length; i++ ){ + files.add( fs[ i ] ); + } + + ListDataEvent event = new ListDataEvent( this, ListDataEvent.CONTENTS_CHANGED, 0, 999999 ); + for ( Iterator it = listeners.iterator(); it.hasNext(); ){ + ListDataListener l = (ListDataListener) it.next(); + l.contentsChanged( event ); + } + } + } + + public void setDirectory( String dir ){ + setDirectory( new File( dir ) ); + } + + public void addListDataListener( ListDataListener listener ){ + listeners.add( listener ); + } + + public Object getElementAt( int index ){ + return this.files.get( index ); + } + + public int getSize(){ + return this.files.size(); + } + + public void removeListDataListener( ListDataListener listener ){ + listeners.remove( listener ); + } + +} diff --git a/editor/src/main/java/com/rafkind/paintown/Lambda0.java b/editor/src/main/java/com/rafkind/paintown/Lambda0.java new file mode 100644 index 000000000..3a174d147 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/Lambda0.java @@ -0,0 +1,13 @@ +package com.rafkind.paintown; + +public abstract class Lambda0{ + public abstract Object invoke() throws Exception; + + public Object invoke_(){ + try{ + return invoke(); + } catch ( Exception e ){ + } + return null; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/Lambda1.java b/editor/src/main/java/com/rafkind/paintown/Lambda1.java new file mode 100644 index 000000000..543de3ad7 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/Lambda1.java @@ -0,0 +1,29 @@ +package com.rafkind.paintown; + +import java.util.List; +import java.util.Iterator; + +public abstract class Lambda1{ + public abstract Object invoke( Object x ) throws Exception; + + public static void foreach( List list, Lambda1 lambda ) throws Exception { + for ( Iterator iterator = list.iterator(); iterator.hasNext(); ){ + lambda.invoke( iterator.next() ); + } + } + + public static void foreach_( List list, Lambda1 lambda ){ + try{ + foreach( list, lambda ); + } catch ( Exception e ){ + } + } + + public Object invoke_( Object x ){ + try{ + return invoke( x ); + } catch ( Exception e ){ + } + return null; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/Lambda2.java b/editor/src/main/java/com/rafkind/paintown/Lambda2.java new file mode 100644 index 000000000..5ec3196a6 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/Lambda2.java @@ -0,0 +1,13 @@ +package com.rafkind.paintown; + +public abstract class Lambda2{ + public abstract Object invoke( Object x, Object y ) throws Exception; + + public Object invoke_( Object x, Object y ){ + try{ + return invoke( x, y ); + } catch ( Exception e ){ + } + return null; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/MaskedImage.java b/editor/src/main/java/com/rafkind/paintown/MaskedImage.java new file mode 100644 index 000000000..24cff421b --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/MaskedImage.java @@ -0,0 +1,82 @@ +package com.rafkind.paintown; + +import java.awt.*; +import java.awt.image.*; +import java.io.*; +import javax.imageio.*; +import java.util.HashMap; + +public class MaskedImage extends BufferedImage { + + private static HashMap images = new HashMap(); + + public MaskedImage( int w, int h ){ + super( w, h, BufferedImage.TYPE_INT_ARGB ); + } + + public MaskedImage( BufferedImage image ){ + this( image.getWidth(), image.getHeight() ); + for ( int x = 0; x < image.getWidth(); x++ ){ + for ( int y = 0; y < image.getHeight(); y++ ){ + int pixel = image.getRGB( x, y ); + this.setRGB( x, y, pixel ); + } + } + } + + public static void clearCache(){ + synchronized( images ){ + images.clear(); + } + } + + public static MaskedImage load(String s) throws IOException { + synchronized (images){ + if (images.get(s) != null){ + return (MaskedImage) images.get(s); + } + } + BufferedImage temp = ImageIO.read( new File( s ) ); + MaskedImage image = new MaskedImage( temp.getWidth(), temp.getHeight() ); + for ( int x = 0; x < temp.getWidth(); x++ ){ + for ( int y = 0; y < temp.getHeight(); y++ ){ + int pixel = temp.getRGB( x, y ); + if ( (pixel & 0x00ffffff) == 0x00ff00ff ){ + /* convert masking color into an alpha channel that is translucent */ + pixel = 0x00ffffff; + } + image.setRGB( x, y, pixel ); + } + } + synchronized (images){ + images.put(s, image); + } + return image; + } + + public static MaskedImage load( String s, HashMap remap ) throws IOException { + String full = s + "\n" + String.valueOf( remap.hashCode() ); + synchronized (images){ + if (images.containsKey(full)){ + return (MaskedImage) images.get(full); + } + } + + MaskedImage image = new MaskedImage(load(s)); + for (int x = 0; x < image.getWidth(); x++){ + for (int y = 0; y < image.getHeight(); y++){ + int pixel = image.getRGB( x, y ); + if ( remap.containsKey( new Integer( pixel ) ) ){ + Integer mapped = (Integer) remap.get( new Integer( pixel ) ); + image.setRGB( x, y, mapped.intValue() ); + } + } + } + + synchronized (images){ + images.put(full, image); + } + + return image; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/MinMaxSpinnerModel.java b/editor/src/main/java/com/rafkind/paintown/MinMaxSpinnerModel.java new file mode 100644 index 000000000..40a04adba --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/MinMaxSpinnerModel.java @@ -0,0 +1,51 @@ +package com.rafkind.paintown; + +import java.util.List; +import java.util.ArrayList; +import java.util.Iterator; + +import javax.swing.SpinnerModel; +import javax.swing.event.ChangeListener; +import javax.swing.event.ChangeEvent; + +public class MinMaxSpinnerModel implements SpinnerModel { + List listeners = new ArrayList(); + int value; + int min; + int max; + + public MinMaxSpinnerModel( int value, int min, int max ){ + this.value = value; + this.min = min; + this.max = max; + } + + public Object getNextValue(){ + return max > value ? new Integer( value + 1 ) : null; + } + + public void addChangeListener( ChangeListener l ){ + listeners.add( l ); + } + + public void removeChangeListener( ChangeListener l ){ + listeners.remove( l ); + } + + public Object getPreviousValue(){ + return value > min ? new Integer( value - 1 ) : null; + } + + public void setValue( Object o ){ + value = ((Integer) o).intValue(); + ChangeEvent event = new ChangeEvent( this ); + for ( Iterator it = listeners.iterator(); it.hasNext(); ){ + ChangeListener change = (ChangeListener) it.next(); + change.stateChanged( event ); + } + } + + public Object getValue(){ + return new Integer( value ); + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/RelativeFileChooser.java b/editor/src/main/java/com/rafkind/paintown/RelativeFileChooser.java new file mode 100644 index 000000000..7be253ad9 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/RelativeFileChooser.java @@ -0,0 +1,269 @@ +package com.rafkind.paintown; + +import javax.swing.*; +import javax.swing.event.*; +import java.awt.event.*; + +import java.util.List; +import java.util.Collections; +import java.util.Iterator; +import java.util.ArrayList; +import java.io.File; + +import org.swixml.SwingEngine; + +public class RelativeFileChooser extends JDialog { + + public static final int OK = 0; + public static final int CANCEL = 1; + + private List paths = new ArrayList(); + private int option = CANCEL; + + public RelativeFileChooser(JFrame frame, String title, File start){ + this(frame, title, start, null); + } + + public RelativeFileChooser(JFrame frame, String title, final File start, File jump){ + super(frame, title, true); + this.setSize(300, 400); + + SwingEngine engine = new SwingEngine("relative.xml"); + JPanel panel = (JPanel) engine.getRootComponent(); + this.getContentPane().add(panel); + + this.addWindowListener(new WindowAdapter(){ + public void windowClosing(WindowEvent e){ + setOption(CANCEL); + } + }); + + class FileList implements ListModel{ + List listeners = new ArrayList(); + List files = new ArrayList(); + + public FileList(File start){ + setFile(start); + } + + public void setFile(File file){ + files = new ArrayList(); + if (file.isDirectory()){ + File[] all = file.listFiles(); + files.add(new File(".")); + files.add(new File("..")); + for (int i = 0; i < all.length; i++){ + files.add(all[i]); + } + } + + ListDataEvent event = new ListDataEvent( this, ListDataEvent.CONTENTS_CHANGED, 0, 999999 ); + for ( Iterator it = listeners.iterator(); it.hasNext(); ){ + ListDataListener l = (ListDataListener) it.next(); + l.contentsChanged(event); + } + } + + public void removeListDataListener(ListDataListener l){ + listeners.remove(l); + } + + public void addListDataListener(ListDataListener l){ + listeners.add(l); + } + + public Object getElementAt(int index){ + return files.get(index); + } + + public int getSize(){ + return files.size(); + } + }; + + File use = start; + if (jump != null){ + use = jump; + if (use.isFile()){ + use = use.getParentFile(); + } + } + + // paths.add( start ); + paths.add(new File(".")); + paths.addAll(pathTo(start, use)); + + final JTextField path = (JTextField) engine.find("path"); + path.setText(getPath()); + + final FileList list = new FileList(use); + + final JList files = (JList) engine.find("files"); + files.setModel(list); + + files.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "select"); + + final Lambda1 updateFile = new Lambda1(){ + public Object invoke(Object o){ + File file = (File) o; + + /* Don't do anything if the user selects the current directory */ + if (file.toString() == "."){ + return null; + } + + /* If they select '..' then remove one path */ + if (file.toString() == ".." && paths.size() > 1){ + paths.remove(paths.size() - 1); + path.setText(getPath()); + } else { + /* Otherwise add the path to the current list */ + paths.add(file); + } + + list.setFile(getTruePath(start)); + path.setText(getPath()); + + /* User has selected a file so theres nothing to do except return it to the user */ + if (! file.isDirectory()){ + setOption(OK); + RelativeFileChooser.this.setVisible(false); + } + + /* Reset the visible list */ + if (list.getSize() > 0){ + files.setSelectedIndex(0); + files.ensureIndexIsVisible(0); + } + + return null; + } + }; + + files.getActionMap().put("select", new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + File file = (File) files.getSelectedValue(); + updateFile.invoke_(file); + } + }); + + files.addMouseListener(new MouseAdapter(){ + public void mouseClicked(MouseEvent clicked){ + if (clicked.getClickCount() == 2){ + Action action = files.getActionMap().get("select"); + action.actionPerformed(new ActionEvent(this, 0, "select")); + } + } + }); + + final JButton up = (JButton) engine.find("up"); + up.addActionListener( new AbstractAction(){ + public void actionPerformed(ActionEvent e){ + updateFile.invoke_(new File("..")); + /* + if (paths.size() > 1){ + paths.remove(paths.size() - 1); + list.setFile(getTruePath(start)); + path.setText(getPath()); + } + */ + } + }); + + final JButton cancel = (JButton) engine.find("cancel"); + cancel.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent e){ + setOption(CANCEL); + RelativeFileChooser.this.setVisible(false); + } + }); + + final JButton ok = (JButton) engine.find("ok"); + ok.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent e){ + File selected = (File) files.getSelectedValue(); + if (selected.getName() == ".."){ + /* Don't allow user to select .. */ + return; + } else if (selected.getName() == "."){ + /* If the user selects . then they want this directory */ + } else { + /* Otherwise they selected a file/directory so use that one */ + paths.add(selected); + } + + /* User has made their choice so we are done */ + setOption(OK); + RelativeFileChooser.this.setVisible(false); + } + }); + } + + /* discovers the relative path between 'base' and 'find'. + * base: /a/b/c + * find: /a/b/c/d/e/f + * output: d/e/f + */ + private List pathTo(File base, File find){ + if (find == null){ + return new ArrayList(); + } + + List out = new ArrayList(); + while (!find.equals(base)){ + /* add the current find path then go one up and loop */ + + if (find.isDirectory()){ + out.add(find); + } + File parent = find.getParentFile(); + /* if the parent is the same as the current directory then we have + * hit the root and have implicitly gone past the base since the + * base should never be the root anyway. + */ + if (parent == null || parent.equals(find)){ + return new ArrayList(); + } + find = parent; + } + /* we started adding from the front of 'find' but that gives the + * wrong order, so reverse it + */ + Collections.reverse(out); + + return out; + } + + private void setOption(int i){ + this.option = i; + } + + private int getOption(){ + return this.option; + } + + public int open(){ + this.show(); + return getOption(); + } + + public File getTruePath(File start){ + for (File file: paths){ + start = new File(start, file.getName()); + } + return start; + } + + public String getPath(){ + StringBuffer b = new StringBuffer(); + for (Iterator it = paths.iterator(); it.hasNext(); ){ + File file = (File) it.next(); + b.append(file.getName()); + if (file.isDirectory()){ + b.append("/"); + } + } + return b.toString(); + } + +} diff --git a/editor/src/main/java/com/rafkind/paintown/Token.java b/editor/src/main/java/com/rafkind/paintown/Token.java new file mode 100644 index 000000000..6f22e2107 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/Token.java @@ -0,0 +1,211 @@ +package com.rafkind.paintown; + +import java.util.*; + +public class Token{ + + private List tokens; + private Token parent; + private String data; + private String currentData; + private int line; + + public Token( Token parent ){ + this( 0, parent, null ); + } + + public Token( int line, Token parent ){ + this( line, parent, null ); + } + + public Token( int line ){ + this( line, (Token) null ); + } + + public Token(){ + this( (Token) null ); + } + + public Token( String data ){ + this( null, data ); + } + + public Token( Token parent, String data ){ + this( 0, parent, data ); + } + + public Token( int line, Token parent, String data ){ + tokens = new ArrayList(); + this.parent = parent; + this.data = data; + this.line = line; + currentData = ""; + } + + public int getLine(){ + return line; + } + + public List getTokens(){ + return tokens; + } + + public int childrenCount(){ + return tokens.size(); + } + + public Iterator iterator(){ + Iterator i = tokens.iterator(); + if ( i.hasNext() ){ + i.next(); + } + return i; + } + + public boolean isDatum(){ + return this.tokens.isEmpty(); + } + + public void endData(){ + if ( ! currentData.equals( "" ) ){ + // System.out.println( "Added " + currentData ); + String stripped = currentData; + if ( stripped.matches( "^\".*\"$" ) ){ + stripped = stripped.substring( 1, stripped.length() - 1 ); + } + // this.tokens.add( new Token( this, currentData ) ); + this.tokens.add( new Token( this, stripped ) ); + currentData = ""; + } + } + + public void addData( char c ){ + currentData += String.valueOf( c ); + } + + public String getName(){ + if ( tokens.isEmpty() ){ + return "no name"; + } + return ((Token) tokens.get( 0 )).getData(); + } + + private String getData(){ + return data; + } + + public Token findToken( String name ){ + /* + for ( Iterator it = this.iterator(); it.hasNext(); ){ + Token t = (Token) it.next(); + if ( t.getName().equals( name ) ){ + return t; + } + } + return null; + */ + List l = findTokens( name ); + if ( l.isEmpty() ){ + return null; + } + return (Token) l.get( 0 ); + } + + public List findTokens( String name ){ + int seperator = name.indexOf( "/" ); + String part = null; + if ( seperator != -1 ){ + part = name.substring( 0, seperator ); + name = name.substring( seperator + 1 ); + } + List all = new ArrayList(); + for ( Iterator it = this.iterator(); it.hasNext(); ){ + Token t = (Token) it.next(); + if ( part == null ){ + if ( t.getName().equals( name ) ){ + all.add( t ); + } + } else { + if ( t.getName().equals( part ) ){ + all.addAll( t.findTokens( name ) ); + } + } + } + return all; + } + + public boolean hasIndex(int index){ + /* First token is the name, second and on are the children. We are interested + * in a child node so subtract 1 from the size to ignore the name. + */ + return index < tokens.size() - 1; + } + + private Token readIndex(int index){ + Iterator it = this.iterator(); + for (int i = 0; i < index; i++){ + it.next(); + } + return (Token) it.next(); + } + + public int readInt(int index){ + return Integer.parseInt(readIndex(index).toString()); + } + + public boolean readBoolean(int index){ + return Boolean.parseBoolean(readIndex(index).toString()); + } + + public double readDouble(int index){ + return Double.parseDouble(readIndex(index).toString()); + } + + public String readString(int index){ + return readIndex(index).toString(); + } + + public Token addToken( Token n ){ + tokens.add( n ); + return this; + } + + public Token addToken( String[] args ){ + Token t = new Token( this ); + for ( int i = 0; i < args.length; i++ ){ + t.addToken( new Token( args[ i ] ) ); + } + return addToken( t ); + } + + public void removeToken( Token n ){ + tokens.remove( n ); + } + + public String toString( int spaces ){ + if ( isDatum() ){ + return getData(); + } + StringBuffer b = new StringBuffer(); + b.append( "\n" ); + for ( int i = 0; i < spaces; i++ ){ + b.append( " " ); + } + b.append( "(" + getName() ); + for ( Iterator it = this.iterator(); it.hasNext(); ){ + Token t = (Token) it.next(); + b.append( " " ); + b.append( t.toString( spaces + 2 ) ); + } + b.append( ")" ); + return b.toString(); + } + + public String toString(){ + return toString( 0 ); + } + + public Token getParent(){ + return this.parent; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/TokenReader.java b/editor/src/main/java/com/rafkind/paintown/TokenReader.java new file mode 100644 index 000000000..de8085010 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/TokenReader.java @@ -0,0 +1,219 @@ +package com.rafkind.paintown; + +import java.util.*; +import java.io.*; +import com.rafkind.paintown.exception.LoadException; + +public class TokenReader{ + + private List tokens; + private Iterator iterator; + + public TokenReader( File f ) throws LoadException { + try{ + tokens = new ArrayList(); + BufferedReader reader = new BufferedReader(new FileReader(f)); + + Token current = null; + int line = 1; + boolean inString = false; + while ( reader.ready() ){ + int i = reader.read(); + if ( i == -1 ){ + break; + } + char c = (char) i; + + if ( inString ){ + if ( c == '"' ){ + inString = false; + current.addData( '"' ); + } else { + if ( current != null ){ + current.addData( c ); + } + } + continue; + } + + switch ( c ){ + case '(' : { + // System.out.println( "Read new token" ); + if ( current == null ){ + current = new Token( line ); + tokens.add( current ); + } else { + current.endData(); + Token n = new Token( line, current ); + current.addToken( n ); + current = n; + } + break; + } + case '"' : { + inString = true; + current.addData( '"' ); + break; + } + case ' ' : + case ' ' : { + /* whitespace */ + if ( current != null ){ + current.endData(); + } + break; + } + /* FIXME: handle @ to ignore whole s-expressions */ + case '#' : + case ';' : { + reader.readLine(); + line += 1; + break; + } + case ')' : { + if ( current == null ){ + throw new LoadException( "Too many ')' found. Line " + line + ". File " + f.getName() ); + } else { + current.endData(); + current = current.getParent(); + } + break; + } + case '\n' : { + line += 1; + if ( current != null ){ + current.endData(); + } + break; + } + case 'a' : + case 'b' : + case 'c' : + case 'd' : + case 'e' : + case 'f' : + case 'g' : + case 'h' : + case 'i' : + case 'j' : + case 'k' : + case 'l' : + case 'm' : + case 'n' : + case 'o' : + case 'p' : + case 'q' : + case 'r' : + case 's' : + case 't' : + case 'u' : + case 'v' : + case 'w' : + case 'x' : + case 'y' : + case 'z' : + + case 'A' : + case 'B' : + case 'C' : + case 'D' : + case 'E' : + case 'F' : + case 'G' : + case 'H' : + case 'I' : + case 'J' : + case 'K' : + case 'L' : + case 'M' : + case 'N' : + case 'O' : + case 'P' : + case 'Q' : + case 'R' : + case 'S' : + case 'T' : + case 'U' : + case 'V' : + case 'W' : + case 'X' : + case 'Y' : + case 'Z' : + + case '/' : + case '\\' : + case '\'' : + case '.' : + case ':' : + case '!' : + case '@' : + case '$' : + case '%' : + case '^' : + case '&' : + case '_' : + case '-' : + case '0' : + case '1' : + case '2' : + case '3' : + case '4' : + case '5' : + case '6' : + case '7' : + case '8' : + case '9' : { + if ( current != null ){ + current.addData( c ); + } + break; + } + } + } + + System.out.println( "Read " + (line - 1) + " lines" ); + + /* + Token f1 = (Token) tokens.get( 0 ); + System.out.println( f1 ); + System.out.println( f1.findToken( "background" ) ); + */ + + reader.close(); + + } catch ( IOException ie ){ + throw new LoadException( "Could not read file", ie ); + } + + filterTokens( tokens, "!" ); + + reset(); + } + + public void reset(){ + iterator = tokens.iterator(); + } + + /* remove any tokens that start with the invalid string */ + private void filterTokens( List tokens, String invalid ){ + List filtered = new ArrayList(); + for ( Iterator it = tokens.iterator(); it.hasNext(); ){ + Token t = (Token) it.next(); + if ( ! t.isDatum() && t.getName().startsWith( invalid ) ){ + filtered.add( t ); + } else if ( ! t.isDatum() ){ + filterTokens( t.getTokens(), invalid ); + } + } + for ( Iterator it = filtered.iterator(); it.hasNext(); ){ + tokens.remove( it.next() ); + } + } + + public Token nextToken() throws LoadException { + if ( ! iterator.hasNext() ){ + throw new LoadException( "No tokens left" ); + } + return (Token) iterator.next(); + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/AnimatedObject.java b/editor/src/main/java/com/rafkind/paintown/animator/AnimatedObject.java new file mode 100644 index 000000000..e848e831a --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/AnimatedObject.java @@ -0,0 +1,80 @@ +package com.rafkind.paintown.animator; + +import java.io.File; + +import java.util.Vector; +import java.util.List; +import java.util.ArrayList; +import java.util.Iterator; + +import com.rafkind.paintown.exception.LoadException; + +import com.rafkind.paintown.Lambda1; + +public abstract class AnimatedObject extends BasicObject { + + private Vector animations = new Vector(); + + /* List of listeners that are notified when animations are + * added/removed from the animations vector. + */ + private List updates = new ArrayList(); + + /* Shared properties for all animations, such as the background color */ + private DrawProperties.Properties drawProperties = new DrawProperties.Properties(); + + public AnimatedObject(String name){ + super(name); + } + + public DrawProperties.Properties getDrawProperties(){ + return drawProperties; + } + + public void addAnimationUpdate(Lambda1 update){ + updates.add(update); + } + + public void removeAnimationUpdate(Lambda1 update){ + updates.remove(update); + } + + public void removeAnimation(int index){ + Animation temp = (Animation) animations.elementAt(index); + animations.removeElement(temp); + updateAnimationListeners(); + } + + public void updateAnimationListeners(){ + for (Iterator it = updates.iterator(); it.hasNext(); /**/){ + Lambda1 update = (Lambda1) it.next(); + try{ + update.invoke(this); + } catch (Exception e){ + e.printStackTrace(); + } + } + } + + abstract public void setMap(int map); + + public void removeAnimation(Animation anim){ + removeAnimation(animations.indexOf(anim)); + } + + public void addAnimation(Animation animation){ + /* Make the animation start at the frame so it shows something immediately */ + animation.nextFrame(); + animations.add(animation); + updateAnimationListeners(); + } + + public Vector getAnimations(){ + return animations; + } + + public Animation getAnimation(int i){ + return (Animation) getAnimations().get(i); + } + +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/Animation.java b/editor/src/main/java/com/rafkind/paintown/animator/Animation.java new file mode 100644 index 000000000..5e4b4444a --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/Animation.java @@ -0,0 +1,810 @@ +package com.rafkind.paintown.animator; + +import java.util.List; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Vector; +import java.util.HashMap; + +import com.rafkind.paintown.animator.events.*; +import com.rafkind.paintown.animator.events.scala.EffectPoint; + +import com.rafkind.paintown.Token; +import com.rafkind.paintown.exception.*; +import com.rafkind.paintown.Lambda1; + +import java.awt.image.*; +import java.awt.Color; + +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.AlphaComposite; +import javax.swing.JComponent; + +public class Animation implements Runnable { + private String name; + private boolean alive = true; + private boolean running; + private List drawables; + private List notifiers; + private Vector events; + private String sequence; + private BufferedImage image; + private BoundingBox attackArea; + private BoundingBox defenseArea; + private int eventIndex; + private double delay; + private int delayTime; + private int x; + private int y; + private int offsetX; + private int offsetY; + private String baseDirectory; + private int range; + private String type; + private double animationSpeed; + /* when something is changed in the animation 'listeners' are notified */ + private List listeners; + private List loopers = new ArrayList(); + private boolean onionSkinning = false; + private int onionSkins = 1; + private boolean onionSkinFront = true; + + private List effectPoints = new ArrayList(); + + private Vector keys; + private HashMap remapData; + + public Animation(){ + drawables = new ArrayList(); + events = new Vector(); + /* give the animation something so it rests a little bit */ + events.add( new NopEvent() ); + notifiers = new ArrayList(); + listeners = new ArrayList(); + image = null; + animationSpeed = 1.0; + sequence = "none"; + attackArea = new BoundingBox( 0, 0, 0, 0 ); + defenseArea = new BoundingBox( 0, 0, 0, 0 ); + keys = new Vector(); + baseDirectory = "."; + type = "none"; + name = "New Animation"; + } + + public Animation(final Animation copy){ + this.name = copy.name; + this.type = copy.type; + this.baseDirectory = copy.baseDirectory; + + animationSpeed = copy.animationSpeed; + sequence = copy.sequence; + drawables = new ArrayList(); + events = new Vector(); + /* give the animation something so it rests a little bit */ + notifiers = new ArrayList(); + listeners = new ArrayList(); + keys = new Vector(); + keys.addAll(copy.keys); + attackArea = new BoundingBox(0, 0, 0, 0); + defenseArea = new BoundingBox(0, 0, 0, 0); + for (AnimationEvent event: copy.events){ + events.add(event.copy()); + } + } + + public Animation(String name){ + this(); + this.name = name; + } + + public Animation(Vector events){ + this(); + this.events = events; + } + + public Animation(Token data) throws LoadException { + this(); + loadData( data ); + } + + public void setType(String s){ + type = s; + + updateAll(); + } + + public String getType(){ + return type; + } + + public void addEffectPoint(EffectPoint point){ + effectPoints.add(point); + } + + public void clearEffectPoints(){ + effectPoints = new ArrayList(); + } + + public void setOnionSkinning(boolean what){ + this.onionSkinning = what; + } + + public boolean isOnionSkinned(){ + return this.onionSkinning; + } + + public int getOnionSkins(){ + return onionSkins; + } + + public void setOnionSkins(int skins){ + onionSkins = skins; + } + + public boolean getOnionSkinFront(){ + return onionSkinFront; + } + + public void setOnionSkinFront(boolean what){ + onionSkinFront = what; + } + + public int getHeight(){ + if ( image != null ){ + return image.getHeight( null ); + } + return 0; + } + + public int getWidth(){ + if ( image != null ){ + return image.getWidth( null ); + } + return 0; + } + + public boolean hasImage(){ + return image != null; + } + + public void setRange( int r ){ + range = r; + + updateAll(); + } + + public int getRange(){ + return range; + } + + /* Returns the index of the added key */ + public int addKey(String key){ + keys.add(key); + updateAll(); + return keys.size() - 1; + } + + public void setMap( HashMap remap ){ + this.remapData = remap; + } + + public HashMap getMap(){ + return this.remapData; + } + + public void setSequence( String s ){ + sequence = s; + updateAll(); + } + + public String getSequence(){ + return sequence; + } + + public void removeKey(int index){ + keys.remove(index); + updateAll(); + } + + public Vector getKeys(){ + return keys; + } + + public String toString(){ + return name != null ? name : "No name set"; + } + + public void setAttack( BoundingBox attack ){ + attackArea = attack; + updateAll(); + } + + public void setDefense( BoundingBox defense ){ + defenseArea = defense; + updateAll(); + } + + private BufferedImage currentImage(){ + return image; + } + + public void setName( String s ){ + this.name = s; + updateAll(); + } + + public String getName(){ + return name; + } + + public void addLoopNotifier(Lambda1 lambda){ + if (!loopers.contains(lambda)){ + loopers.add(lambda); + } + } + + public void removeLoopNotifier(Lambda1 lambda){ + loopers.remove(lambda); + } + + public void addEventNotifier(Lambda1 lambda){ + notifiers.add(lambda); + } + + public void removeEventNotifier(Lambda1 lambda){ + notifiers.remove(lambda); + } + + public synchronized void setImage(BufferedImage image){ + this.image = image; + updateDrawables(); + updateAll(); + } + + public void forceRedraw(){ + updateDrawables(); + } + + public void addChangeUpdate(Lambda1 update){ + listeners.add(update); + } + + public void removeChangeUpdate(Lambda1 update){ + listeners.remove(update); + } + + private void updateAll(){ + for (Iterator it = listeners.iterator(); it.hasNext();){ + Lambda1 update = (Lambda1) it.next(); + update.invoke_(this); + } + } + + /* tell the components that know about this animation that its time + * to redraw the canvas. + * for things like updating the current frame or the attack box + */ + private void updateDrawables(){ + synchronized (drawables){ + for (Iterator it = drawables.iterator(); it.hasNext();){ + JComponent draw = (JComponent) it.next(); + draw.repaint(); + } + } + } + + public synchronized void kill(){ + alive = false; + } + + private synchronized boolean isAlive(){ + return alive; + } + + private synchronized boolean isRunning(){ + return running; + } + + public synchronized void stopRunning(){ + running = false; + } + + public synchronized void startRunning(){ + running = true; + } + + public int addEvent(AnimationEvent e, int index){ + synchronized (events){ + events.add(index, e); + return index; + } + } + + public int addEvent(AnimationEvent e){ + synchronized (events){ + return addEvent(e, events.size()); + } + } + + public void clearEvents(){ + synchronized(events){ + events.clear(); + } + } + + /* swap position of e1 and e2 within event structure */ + public void swapEvents(int e1, int e2){ + synchronized(events){ + AnimationEvent a1 = events.get(e1); + AnimationEvent a2 = events.get(e2); + events.set(e1, a2); + events.set(e2, a1); + } + + } + + /* return a new list that contains the current events + * do not edit the returned list, to add new events use + * addEvent/removeEvent + */ + public Vector getEvents(){ + synchronized(events){ + return new Vector(events); + } + } + + public void removeEvent(AnimationEvent e){ + synchronized (events){ + events.remove(e); + } + } + + public void removeEvent(int i){ + synchronized (events){ + events.remove(i); + } + } + + public String getBaseDirectory(){ + return baseDirectory; + } + + public void setBaseDirectory( String b ){ + baseDirectory = b; + } + + public void setAnimationSpeed(double n){ + animationSpeed = n; + } + + public double getAnimationSpeed(){ + return animationSpeed; + } + + /* draws a skin of the animation upto the given event */ + private void drawSkin(Graphics graphics, int x, int y, int event){ + Animation clone = new Animation(); + clone.setBaseDirectory(getBaseDirectory()); + for (int use = 0; use <= event; use++){ + clone.updateEvent((AnimationEvent) events.get(use)); + } + clone.draw(graphics, x, y); + } + + private boolean isFrameEvent(AnimationEvent event){ + /* sort of a hack to use instanceof */ + return event instanceof com.rafkind.paintown.animator.events.scala.FrameEvent; + } + + private void drawOnionSkins(Graphics graphics, int x, int y, int skins){ + Graphics2D translucent = (Graphics2D) graphics.create(); + int direction = -1; + if (skins < 0){ + direction = 1; + } + + synchronized (events){ + if (events.isEmpty()){ + return; + } + + int here = (eventIndex + direction + events.size()) % events.size(); + int skinsLeft = skins; + while (skinsLeft != 0 && here != eventIndex){ + AlphaComposite newComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) skinsLeft / (float) skins * (float) 0.5); + translucent.setComposite(newComposite); + + AnimationEvent event = (AnimationEvent) events.get(here); + /* sort of a hack to use instanceof */ + if (isFrameEvent(event)){ + skinsLeft += direction; + drawSkin(translucent, x, y, here); + } + here = (here + direction + events.size()) % events.size(); + } + } + } + + private void resetPosition(){ + this.x = 0; + this.y = 0; + this.offsetX = 0; + this.offsetY = 0; + } + + private void drawEffectPoints(Graphics graphics, int x, int y){ + Graphics2D g2d = (Graphics2D) graphics.create(); + int height = g2d.getFontMetrics(g2d.getFont()).getHeight(); + for (EffectPoint effect: effectPoints){ + g2d.setColor(new Color(0, 0, 255)); + g2d.fillOval(x + effect.x(), y + effect.y(), 1, 1); + g2d.setColor(new Color(255, 255, 255)); + g2d.drawOval(x + effect.x() - 2, y + effect.y() - 2, 4, 4); + + int width = g2d.getFontMetrics(g2d.getFont()).stringWidth(effect.name()); + g2d.drawString(effect.name(), + x - width / 2 + effect.x(), + y - height / 2 + effect.y()); + } + } + + public synchronized void draw(Graphics g, int x, int y){ + int trueX = x + this.x + this.offsetX - getWidth() / 2; + int trueY = y + this.y + this.offsetY - getHeight(); + + if (isOnionSkinned() && !getOnionSkinFront()){ + drawOnionSkins(g, x, y, getOnionSkins()); + } + + if (currentImage() != null){ + g.drawImage(currentImage(), trueX, trueY, null); + } + + if (isOnionSkinned() && getOnionSkinFront()){ + drawOnionSkins(g, x, y, getOnionSkins()); + } + + if (getRange() != 0){ + Color old = g.getColor(); + g.setColor(new Color(255, 255, 255)); + g.fillRect(x + this.x, y + this.y, getRange(), 5); + g.setColor(old); + } + + if (! attackArea.empty()){ + attackArea.render(g, trueX, trueY, new Color(236, 30, 30)); + } + + if (! defenseArea.empty()){ + defenseArea.render(g, trueX, trueY, new Color(51, 133, 243)); + } + + drawEffectPoints(g, trueX + getWidth() / 2, trueY + getHeight()); + } + + public synchronized void setImageX( int x ){ + this.x = x; + } + + public synchronized void setImageY( int y ){ + this.y = y; + } + + public synchronized void moveX( int x ){ + this.x += x; + } + + public synchronized void moveY( int y ){ + this.y += y; + } + + public synchronized int getX(){ + return x; + } + + public synchronized int getY(){ + return y; + } + + public synchronized void setOffsetX( int x ){ + this.offsetX = x; + } + + public synchronized int getOffsetX(){ + return this.offsetX; + } + + public synchronized void setOffsetY(int y){ + this.offsetY = y; + } + + public synchronized int getOffsetY(){ + return this.offsetY; + } + + public void addDrawable(JComponent draw){ + synchronized (drawables){ + drawables.add(draw); + } + } + + public void removeDrawable(JComponent draw){ + synchronized (drawables){ + drawables.remove(draw); + } + } + + private void rest(int ms){ + try{ + Thread.sleep(ms); + } catch (Exception e){ + } + } + + /* runs through all the events again */ + public void reset(){ + applyEvents(); + } + + public void applyEvents(){ + resetPosition(); + for (int event = 0; event <= eventIndex; event++){ + AnimationEvent use = (AnimationEvent) events.get(event); + updateEvent(use); + } + } + + private void findNextFrame(int direction){ + int last = eventIndex; + clearEffectPoints(); + synchronized (events){ + eventIndex = (eventIndex + direction + events.size()) % events.size(); + while (eventIndex != last && !isFrameEvent((AnimationEvent) events.get(eventIndex))){ + eventIndex = (eventIndex + direction + events.size()) % events.size(); + } + + applyEvents(); + } + } + + public void previousFrame(){ + findNextFrame(-1); + } + + public void nextFrame(){ + findNextFrame(1); + } + + private int previous(int index, int max){ + int last = index - 1; + if (last < 0){ + last = max - 1; + } + return last; + } + + private int next(int index, int max){ + int next = index + 1; + if (next >= max){ + next = 0; + } + return next; + } + + private void updateEvent(AnimationEvent event){ + event.interact(this); + for (Iterator it = notifiers.iterator(); it.hasNext();){ + Lambda1 lambda = (Lambda1) it.next(); + lambda.invoke_(event); + } + } + + /* can be called to step backward through the animation */ + public void previousEvent(){ + synchronized (events){ + if (! events.isEmpty()){ + if (isFrameEvent((AnimationEvent) events.get(eventIndex))){ + clearEffectPoints(); + } + + eventIndex = previous(eventIndex, events.size()); + if (eventIndex == 0){ + setImageX(0); + setImageY(0); + } + updateEvent((AnimationEvent) events.get(eventIndex)); + } + } + } + + /* Called when the animation loops */ + private void notifyLoopers(){ + for (Lambda1 loop: loopers){ + loop.invoke_(this); + } + } + + /* can be called to step foreward through the animation */ + public void nextEvent(){ + synchronized (events){ + if ( ! events.isEmpty() ){ + if (isFrameEvent((AnimationEvent) events.get(eventIndex))){ + clearEffectPoints(); + } + + eventIndex = next(eventIndex, events.size()); + if (eventIndex == 0){ + setImageX(0); + setImageY(0); + notifyLoopers(); + } + updateEvent((AnimationEvent) events.get(eventIndex)); + } + } + } + + public int getEventIndex(){ + return eventIndex; + } + + private void checkIndex(){ + if (eventIndex < 0){ + eventIndex = 0; + } + if (eventIndex >= events.size()){ + eventIndex = events.size() - 1; + } + } + + public void nextEvent(AnimationEvent event){ + synchronized (events){ + checkIndex(); + if (events.contains(event)){ + while ((AnimationEvent) events.get(eventIndex) != event){ + nextEvent(); + } + } + } + } + + public void setDelay(double delay){ + this.delay = delay; + } + + public double getDelay(){ + return delay; + } + + public int getDelayTime(){ + return delayTime; + } + + public void setDelayTime(int i){ + delayTime = i; + } + + public void delay(){ + delayTime = (int)(getDelay() * getAnimationSpeed()); + } + + /* if the animation doesnt contain something that will cause delay this + * thread will just chew up cpu time + */ + public void run(){ + while (isAlive()){ + if (isRunning() && ! events.isEmpty()){ + nextEvent(); + if (getDelayTime() != 0){ + rest((int) getDelayTime()); + } + setDelayTime(0); + } else { + rest(200); + } + } + } + + public void loadData(Token token) throws LoadException { + if ( ! token.getName().equals( "anim" ) ){ + throw new LoadException( "Starting token is not 'anim'" ); + } + + Token nameToken = token.findToken( "name" ); + if ( nameToken != null ){ + setName( nameToken.readString(0) ); + } + + Token sequenceToken = token.findToken( "sequence" ); + if ( sequenceToken != null ){ + setSequence( sequenceToken.readString( 0 ) ); + } + + Token typeToken = token.findToken( "type" ); + if ( typeToken != null ){ + setType( typeToken.readString( 0 ) ); + } + + Token keyToken = token.findToken( "keys" ); + if ( keyToken != null ){ + for (Iterator it = keyToken.iterator(); it.hasNext(); ){ + String key = ((Token)it.next()).toString(); + keys.add(key); + } + /* + try{ + for(int i = 0; ; i += 1 ){ + String temp = keyToken.readString(i); + if ( temp != null ){ + keys.add( temp ); + } else { + break; + } + } + } catch(Exception e) { + e.printStackTrace(); + } + */ + } + + Token rangeToken = token.findToken( "range" ); + if ( rangeToken != null ){ + range = rangeToken.readInt(0); + } + + Token basedirToken = token.findToken( "basedir" ); + if ( basedirToken != null ){ + setBaseDirectory( basedirToken.readString( 0 ) ); + } + + events.clear(); + for ( Iterator it = token.getTokens().iterator(); it.hasNext(); ){ + Token t = (Token) it.next(); + AnimationEvent ae = EventFactory.getEvent(t.getName()); + if( ae != null ){ + ae.loadToken(t); + events.add( ae ); + } + } + } + + public Token getToken(){ + Token token = new Token(); + token.addToken( new Token( "anim" ) ); + + token.addToken(new String[]{"name", "\"" + name + "\""}); + if ( ! type.equals("none") ){ + token.addToken(new String[]{"type", type}); + } + + if ( ! sequence.equals( "none" ) ){ + token.addToken( new String[]{ "sequence", "\"" + sequence + "\"", "junk" } ); + } + + if ( ! keys.isEmpty() ){ + Token keyToken = new Token( "keys" ); + keyToken.addToken( new Token( "keys")); + for ( Iterator it = keys.iterator(); it.hasNext(); ){ + String key = (String) it.next(); + keyToken.addToken( new Token( key ) ); + } + token.addToken( keyToken ); + } + + if ( range != 0 ){ + token.addToken(new String[]{"range", Integer.toString(range)}); + } + + if( ! baseDirectory.equals( "" ) ){ + token.addToken(new String[]{"basedir", baseDirectory}); + } + + for ( Iterator it = events.iterator(); it.hasNext(); ){ + AnimationEvent event = (AnimationEvent) it.next(); + token.addToken( event.getToken() ); + } + + return token; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/AnimationCanvas.java b/editor/src/main/java/com/rafkind/paintown/animator/AnimationCanvas.java new file mode 100644 index 000000000..fede947ae --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/AnimationCanvas.java @@ -0,0 +1,1118 @@ +package com.rafkind.paintown.animator; + +/* This class models the area in the animator that contains the view area and + * all the widgets associated with it. + */ + +import com.rafkind.paintown.Undo; +import com.rafkind.paintown.Lambda0; +import com.rafkind.paintown.Lambda1; +import com.rafkind.paintown.Lambda2; + +import scala.runtime.AbstractFunction0; +import scala.runtime.BoxedUnit; + +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.DataFlavor; +import javax.activation.DataHandler; + +import com.rafkind.paintown.animator.events.AnimationEvent; +import com.rafkind.paintown.animator.events.EventFactory; +import com.rafkind.paintown.animator.events.OffsetEvent; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.event.*; +import javax.swing.Timer; +import java.io.*; + +import java.util.Iterator; +import java.util.ArrayList; +import java.util.List; +import java.util.Vector; + +import scala.collection.JavaConversions; + +import org.swixml.SwingEngine; + +public abstract class AnimationCanvas extends JPanel { + + protected abstract JComponent makeProperties(AnimatedObject object, Animation animation, Lambda2 changeName); + + private static class ObjectBox{ + public ObjectBox(){} + public synchronized void set(Data x){ obj = x; } + public synchronized Data get(){ return obj; } + private Data obj; + } + + /* Functions that should be called when this object is being destroyed */ + private List cleanupFunctions = new ArrayList(); + + public AnimationCanvas(AnimatedObject object, Animation animation, Lambda2 changeName, final Detacher detacher){ + setLayout(new GridBagLayout()); + + GridBagConstraints constraints = new GridBagConstraints(); + + constraints.gridx = 0; + constraints.gridy = 0; + constraints.weightx = 1; + constraints.weighty = 1; + constraints.fill = GridBagConstraints.BOTH; + constraints.anchor = GridBagConstraints.NORTHWEST; + + this.add(createPane(object, animation, changeName, detacher), constraints); + } + + public void destroy(){ + for (Lambda0 cleanup: cleanupFunctions){ + cleanup.invoke_(); + } + } + + private JPanel createPane(final AnimatedObject object, final Animation animation, final Lambda2 changeName, final Detacher detacher){ + final SwingEngine animEditor = new SwingEngine("animator/animation.xml"); + + final JTabbedPane tabs = (JTabbedPane) animEditor.find("tabs"); + + final DrawArea area = new DrawArea(object.getDrawProperties(), new Lambda0(){ + public Object invoke(){ + return null; + } + }); + + final JButton popOut = (JButton) animEditor.find("pop-out"); + popOut.addActionListener(new AbstractAction(){ + /* detach if true, otherwise attach */ + boolean mode = true; + + /* might be called by the window being destroyed */ + private void reAttach(){ + popOut.setText("Pop out"); + detacher.attach(AnimationCanvas.this, animation.getName()); + mode = ! mode; + } + + public void actionPerformed(ActionEvent event){ + if (mode){ + mode = ! mode; + JFrame frame = detacher.detach(AnimationCanvas.this, animation.getName()); + frame.addWindowListener(new WindowAdapter(){ + public void windowClosing(WindowEvent event){ + reAttach(); + } + }); + + popOut.setText("Put back"); + } else { + reAttach(); + } + + } + }); + + JComponent properties = makeProperties(object, animation, changeName); + if (properties != null){ + tabs.addTab("Properties", properties); + } + tabs.addTab("Events", makeEvents(animation, area)); + // this.save = object; + + JPanel canvas = (JPanel) animEditor.find("canvas"); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.gridx = 0; + constraints.gridy = 0; + constraints.weightx = 1; + constraints.weighty = 1; + constraints.fill = GridBagConstraints.BOTH; + constraints.anchor = GridBagConstraints.NORTHWEST; + + canvas.add(area, constraints); + + final JSplitPane split = (JSplitPane) animEditor.find("split"); + SwingUtilities.invokeLater(new Runnable(){ + public void run(){ + /* hack to set the divider location */ + if (split.getWidth() != 0){ + split.setDividerLocation(0.6); + } else { + SwingUtilities.invokeLater(this); + } + } + }); + // split.setDividerLocation(0.6); + + // SwingEngine contextEditor = new SwingEngine ( "animator/animation.xml"); + final SwingEngine contextEditor = animEditor; + + SwingEngine controlEditor = new SwingEngine("animator/controls.xml"); + + JButton displayToken = (JButton) controlEditor.find( "token" ); + + displayToken.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + final JDialog tempDiag = new JDialog(); + tempDiag.setSize(400,400); + final JTextArea tempText = new JTextArea(); + final JScrollPane tempPane = new JScrollPane(tempText); + tempDiag.add(tempPane); + tempText.setText(animation.getToken().toString()); + tempDiag.show(); + } + }); + + JButton stopAnim = (JButton) animEditor.find("stop"); + stopAnim.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + animation.stopRunning(); + } + }); + + JButton playAnim = (JButton) animEditor.find("play"); + playAnim.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + animation.startRunning(); + } + }); + + JButton previousFrame = (JButton) animEditor.find("previous-frame"); + JButton nextFrame = (JButton) animEditor.find("next-frame"); + + previousFrame.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + animation.previousFrame(); + animation.forceRedraw(); + } + }); + + nextFrame.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + animation.nextFrame(); + animation.forceRedraw(); + } + }); + + tabs.addTab("Tools", makeTools(object, area, animation)); + + // setupTools((JComboBox) contextEditor.find("tools"), (JPanel) contextEditor.find("tool-area"), object, area, animation); + + area.animate(animation); + + return (JPanel) animEditor.getRootComponent(); + } + + private void addTool(Box box, String name, JComponent tool){ + final JPanel panel = new JPanel(new GridBagLayout()); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.gridx = 0; + constraints.gridy = 0; + constraints.weightx = 1; + constraints.weighty = 1; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.NORTHWEST; + box.add(panel); + panel.add(tool, constraints); + panel.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder(name), + BorderFactory.createEmptyBorder(5,5,5,5))); + } + + private JScrollPane makeTools(final AnimatedObject object, final DrawArea area, final Animation animation){ + final Box box = Box.createVerticalBox(); + final JScrollPane tools = new JScrollPane(box); + + addTool(box, "Background Color", Tools.makeBackgroundTool(object, area)); + addTool(box, "Grid", Tools.makeGridTool(area)); + addTool(box, "Overlay Animation", makeOverlayAnimation(object, area)); + addTool(box, "Overlay Image", Tools.makeOverlayImageTool(AnimationCanvas.this, area)); + addTool(box, "Speed and Scale", makeSpeedAndScale(animation, area)); + + return tools; + } + + private void setupTools(final JComboBox tools, final JPanel toolPane, final AnimatedObject object, final DrawArea area, final Animation animation){ + final String chooseNone = "None"; + final String chooseBackground = "Background Color"; + final String chooseGrid = "Grid"; + final String chooseOverlayAnimation = "Overlay Animation"; + final String chooseOverlayImage = "Overlay Image"; + final String chooseSpeedAndScale = "Speed and Scale"; + tools.addItem(chooseNone); + tools.addItem(chooseSpeedAndScale); + tools.addItem(chooseBackground); + tools.addItem(chooseGrid); + tools.addItem(chooseOverlayAnimation); + tools.addItem(chooseOverlayImage); + + tools.addActionListener(new AbstractAction(){ + /* TODO: If there are too many tools then create them lazily so we + * don't spend too much time creating the animation pane on startup. + */ + final JPanel toolNone = new JPanel(); + final JPanel toolBackground = Tools.makeBackgroundTool(object, area); + final JPanel toolGrid = Tools.makeGridTool(area); + final JPanel toolOverlay = makeOverlayAnimation(object, area); + final JPanel toolOverlayImage = Tools.makeOverlayImageTool(AnimationCanvas.this, area); + final JPanel toolSpeedAndScale = makeSpeedAndScale(animation, area); + + private JPanel getTool(String name){ + if (name.equals(chooseNone)){ + return toolNone; + } + if (name.equals(chooseSpeedAndScale)){ + return toolSpeedAndScale; + } + if (name.equals(chooseBackground)){ + return toolBackground; + } + if (name.equals(chooseOverlayImage)){ + return toolOverlayImage; + } + if (name.equals(chooseOverlayAnimation)){ + return toolOverlay; + } + if (name.equals(chooseGrid)){ + return toolGrid; + } + + throw new RuntimeException("No such tool with name '" + name + "'"); + } + + public void actionPerformed(ActionEvent event){ + toolPane.removeAll(); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.gridx = 0; + constraints.gridy = 0; + constraints.weightx = 1; + constraints.weighty = 1; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.NORTHWEST; + toolPane.add(getTool((String) tools.getSelectedItem()), constraints); + toolPane.revalidate(); + AnimationCanvas.this.revalidate(); + } + }); + } + + private JPanel makeSpeedAndScale(final Animation animation, final DrawArea area){ + final SwingEngine context = new SwingEngine("animator/tool-speed-scale.xml"); + final JLabel animationSpeed = (JLabel) context.find("speed-num"); + animationSpeed.setText("Animation speed: " + (int) (animation.getAnimationSpeed() * 100) + "%"); + final JSlider speed = (JSlider) context.find("speed"); + final double speedNumerator = 20.0; + speed.setValue( (int) (speedNumerator / animation.getAnimationSpeed()) ); + speed.addChangeListener( new ChangeListener(){ + public void stateChanged( ChangeEvent e ){ + animation.setAnimationSpeed(speedNumerator / speed.getValue()); + animationSpeed.setText("Animation speed: " + (int) (speed.getValue() / speedNumerator * 100) + "%"); + } + }); + + final JButton speedIncrease = (JButton) context.find("speed:increase"); + final JButton speedDecrease = (JButton) context.find("speed:decrease"); + + speedIncrease.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + adjustSlider(speed, +1); + } + }); + + speedDecrease.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + adjustSlider(speed, -1); + } + }); + + final JLabel scaleNum = (JLabel) context.find("scale-num"); + /* FIXME: abstract out setting the scale test because its duplicated + * 3 times. + */ + scaleNum.setText("Scale: " + (int)(area.getScale() * 100) + "%"); + final JSlider scale = (JSlider) context.find( "scale" ); + scale.setValue((int)(area.getScale() * 5.0)); + scale.addChangeListener(new ChangeListener(){ + public void stateChanged(ChangeEvent e){ + area.setScale(scale.getValue() / 5.0); + scaleNum.setText("Scale: " + (int)(100 * area.getScale()) + "%"); + } + }); + + area.addScaleListener(new Lambda0(){ + public Object invoke(){ + scale.setValue((int)(area.getScale() * 5.0)); + scaleNum.setText("Scale: " + (int)(100 * area.getScale()) + "%"); + scaleNum.repaint(); + scale.repaint(); + return null; + } + }); + + final JButton scaleIncrease = (JButton) context.find("scale:increase"); + final JButton scaleDecrease = (JButton) context.find("scale:decrease"); + + scaleIncrease.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + adjustSlider(scale, +1); + } + }); + + scaleDecrease.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + adjustSlider(scale, -1); + } + }); + + return (JPanel) context.getRootComponent(); + } + + private JPanel makeOverlayAnimation(final AnimatedObject object, final DrawArea area){ + final SwingEngine context = new SwingEngine("animator/tool-overlay-animation.xml"); + final ObjectBox current = new ObjectBox(); + final JComboBox animations = (JComboBox) context.find("animations"); + animations.addItem(null); + for (Animation animation: object.getAnimations()){ + animations.addItem(animation); + } + + final Lambda1 updateAnimations = new Lambda1(){ + public Object invoke(Object objectSelf){ + animations.removeAllItems(); + animations.addItem(null); + for (Animation animation: object.getAnimations()){ + animations.addItem(animation); + } + return null; + } + }; + + object.addAnimationUpdate(updateAnimations); + + cleanupFunctions.add(new Lambda0(){ + public Object invoke(){ + object.removeAnimationUpdate(updateAnimations); + return null; + } + }); + + animations.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + Animation animation = (Animation) animations.getSelectedItem(); + if (current.get() != null){ + current.get().removeDrawable(area); + } + current.set(animation); + area.setOverlay(animation); + if (animation != null){ + animation.addDrawable(area); + } + } + }); + + final JSpinner offsetx = (JSpinner) context.find("x-offset"); + final JSpinner offsety = (JSpinner) context.find("y-offset"); + + offsetx.setValue(new Integer(area.getOverlayAnimationOffsetX())); + offsetx.addChangeListener(new ChangeListener(){ + public void stateChanged(ChangeEvent event){ + area.setOverlayAnimationOffsetX(((Integer) offsetx.getValue()).intValue()); + area.repaint(); + } + }); + + offsety.setValue(new Integer(area.getOverlayAnimationOffsetY())); + offsety.addChangeListener(new ChangeListener(){ + public void stateChanged(ChangeEvent event){ + area.setOverlayAnimationOffsetY(((Integer) offsety.getValue()).intValue()); + area.repaint(); + } + }); + + final JCheckBox flipx = (JCheckBox) context.find("flip-x"); + final JCheckBox flipy = (JCheckBox) context.find("flip-y"); + + flipx.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + area.setOverlayAnimationFlipX(flipx.isSelected()); + area.repaint(); + } + }); + + flipy.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + area.setOverlayAnimationFlipY(flipy.isSelected()); + area.repaint(); + } + }); + + JButton stop = (JButton) context.find("stop"); + stop.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + Animation animation = current.get(); + if (animation != null){ + animation.stopRunning(); + } + } + }); + + JButton play = (JButton) context.find("play"); + play.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + Animation animation = current.get(); + if (animation != null){ + animation.startRunning(); + } + } + }); + + final JLabel alphaText = (JLabel) context.find("alpha-text"); + final JSlider alpha = (JSlider) context.find("alpha"); + + alpha.setValue((int)(area.getOverlayAlpha() * alpha.getMaximum())); + alphaText.setText("Transparency " + area.getOverlayAlpha()); + alpha.addChangeListener(new ChangeListener(){ + public void stateChanged(ChangeEvent change){ + area.setOverlayAlpha((double) alpha.getValue() / (double) alpha.getMaximum()); + alphaText.setText("Transparency " + area.getOverlayAlpha()); + area.repaint(); + } + }); + + JButton nextFrame = (JButton) context.find("next-frame"); + JButton previousFrame = (JButton) context.find("previous-frame"); + + previousFrame.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + Animation animation = current.get(); + if (animation != null){ + animation.previousFrame(); + animation.forceRedraw(); + } + } + }); + + nextFrame.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + Animation animation = current.get(); + if (animation != null){ + animation.nextFrame(); + animation.forceRedraw(); + } + } + }); + + final JRadioButton front = (JRadioButton) context.find("front"); + final JRadioButton back = (JRadioButton) context.find("back"); + front.setActionCommand("front"); + back.setActionCommand("back"); + + AbstractAction change = new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + if (event.getActionCommand().equals("front")){ + area.setOverlayFront(); + } else { + area.setOverlayBehind(); + } + area.repaint(); + } + }; + + front.addActionListener(change); + back.addActionListener(change); + + return (JPanel) context.getRootComponent(); + } + + private JComponent makeEvents(final Animation animation, final DrawArea area){ + final SwingEngine contextEditor = new SwingEngine("animator/animation-events.xml"); + // final JScrollPane eventScroll = (JScrollPane) contextEditor.find("event-scroll"); + // eventScroll.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT); + + final JList eventList = (JList) contextEditor.find("events"); + + final Lambda0 updateEventList = new Lambda0(){ + private Lambda0 update = new Lambda0(){ + public Object invoke(){ + eventList.repaint(); + return null; + } + }; + public Object invoke(){ + eventList.setListData(animation.getEvents()); + for (AnimationEvent event: animation.getEvents()){ + event.addUpdateListener(update); + } + + return null; + } + }; + + eventList.setDragEnabled(true); + updateEventList.invoke_(); + eventList.setDropMode(DropMode.ON); + eventList.setTransferHandler(new TransferHandler(){ + private int toRemove = -1; + + public boolean canImport(TransferHandler.TransferSupport support){ + // System.out.println("Can I import " + support); + /* bleh */ + return true; + } + + protected void exportDone(JComponent source, Transferable data, int action){ + // System.out.println("did export action was " + action); + if (action == TransferHandler.MOVE){ + // System.out.println("Removing event " + toRemove); + animation.removeEvent(toRemove); + updateEventList.invoke_(); + } + } + + public int getSourceActions(JComponent component){ + // System.out.println("What are my actions? " + TransferHandler.MOVE); + return TransferHandler.MOVE; + } + + protected Transferable createTransferable(JComponent component){ + // System.out.println("make a transferable"); + return new Transferable(){ + final Object index = eventList.getSelectedValue(); + public Object getTransferData(DataFlavor flavor){ + return index; + } + + public DataFlavor[] getTransferDataFlavors(){ + try{ + DataFlavor[] flavors = new DataFlavor[1]; + flavors[0] = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType); + return flavors; + } catch (ClassNotFoundException ex){ + System.out.println(ex); + return null; + } + } + + public boolean isDataFlavorSupported(DataFlavor flavor){ + return true; + } + }; + } + + public boolean importData(TransferSupport supp) { + // System.out.println("Try to import " + supp); + if (!canImport(supp)) { + return false; + } + + try{ + JList.DropLocation location = (JList.DropLocation) supp.getDropLocation(); + // System.out.println("Drop location to " + location); + toRemove = eventList.getSelectedIndex(); + int index = location.getIndex(); + if (index < toRemove){ + toRemove += 1; + } + AnimationEvent event = (AnimationEvent) supp.getTransferable().getTransferData(new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType)); + animation.addEvent(event, index); + updateEventList.invoke_(); + eventList.setSelectedIndex(index); + } catch (ClassNotFoundException exception){ + System.err.println(exception); + } catch (java.awt.datatransfer.UnsupportedFlavorException exception){ + System.err.println(exception); + } catch (IOException exception){ + System.err.println(exception); + } + + return true; + } + + }); + + final ObjectBox currentEvent = new ObjectBox(); + + animation.addEventNotifier(new Lambda1(){ + public Object invoke(Object event){ + currentEvent.set(event); + eventList.repaint(); + return null; + } + }); + + eventList.addListSelectionListener(new ListSelectionListener(){ + public void valueChanged(ListSelectionEvent e){ + AnimationEvent event = (AnimationEvent) eventList.getSelectedValue(); + animation.stopRunning(); + animation.nextEvent(event); + currentEvent.set(event); + } + }); + + final ObjectBox lastEvent = new ObjectBox(); + final Lambda1 doEvent = new Lambda1(){ + final JPanel work = (JPanel) contextEditor.find("event-work"); + public Object invoke(Object _event){ + if (lastEvent.get() != null){ + AnimationEvent last = (AnimationEvent) lastEvent.get(); + last.destroy(); + } + AnimationEvent event = (AnimationEvent) _event; + lastEvent.set(event); + JPanel editor = event.getEditor(animation, area); + work.removeAll(); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.gridx = 0; + constraints.gridy = 0; + constraints.weightx = 1; + constraints.weighty = 1; + constraints.fill = GridBagConstraints.BOTH; + constraints.anchor = GridBagConstraints.NORTHWEST; + work.add(editor, constraints); + work.revalidate(); + return null; + } + }; + + eventList.addMouseListener( new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if ( rightClick( e ) || e.getClickCount() == 2 ){ + int index = eventList.locationToIndex(e.getPoint()); + AnimationEvent event = (AnimationEvent) animation.getEvents().elementAt(index); + doEvent.invoke_(event); + } + } + }); + + eventList.setCellRenderer(new DefaultListCellRenderer(){ + public Component getListCellRendererComponent( + JList list, + Object value, + int index, + boolean isSelected, + boolean cellHasFocus){ + + setText(((AnimationEvent)value).getName()); + setBackground(isSelected ? Color.gray : Color.white); + if (currentEvent.get() == value){ + setForeground(Color.blue); + } else { + setForeground(isSelected ? Color.white : Color.black); + } + return this; + } + }); + + // Need to add events to this combobox from event factory + // EventFactory.init(); + final JComboBox eventSelect = (JComboBox) contextEditor.find( "event-select" ); + for (Iterator it = EventFactory.getNames().iterator(); it.hasNext();){ + String event = (String) it.next(); + eventSelect.addItem(event); + } + + final JComboBox tools = (JComboBox) contextEditor.find("tools"); + final JPanel toolPane = (JPanel) contextEditor.find("tool-area"); + final String chooseNone = "None"; + final String chooseOnionSkinning = "Onion skinning"; + final String chooseAdjustOffsets = "Adjust offsets"; + tools.addItem(chooseNone); + tools.addItem(chooseOnionSkinning); + tools.addItem(chooseAdjustOffsets); + + tools.addActionListener(new AbstractAction(){ + final JPanel toolNone = new JPanel(); + final JPanel toolOnionSkinning = makeOnionSkinningPanel(animation); + final JPanel toolAdjustOffsets = makeAdjustOffsetsPanel(animation); + + private JPanel getTool(String name){ + if (name.equals(chooseNone)){ + return toolNone; + } + if (name.equals(chooseOnionSkinning)){ + return toolOnionSkinning; + } + if (name.equals(chooseAdjustOffsets)){ + return toolAdjustOffsets; + } + + throw new RuntimeException("No such tool with name '" + name + "'"); + } + + public void actionPerformed(ActionEvent event){ + toolPane.removeAll(); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.gridx = 0; + constraints.gridy = 0; + constraints.weightx = 1; + constraints.weighty = 1; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.NORTHWEST; + toolPane.add(getTool((String) tools.getSelectedItem()), constraints); + toolPane.revalidate(); + + } + }); + + JButton addAllFrames = (JButton) contextEditor.find("add-frames"); + addAllFrames.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + for (String path: JavaConversions.asJavaCollection(NewAnimator.getFiles(animation.getBaseDirectory()))){ + com.rafkind.paintown.animator.events.scala.FrameEvent frame = new com.rafkind.paintown.animator.events.scala.FrameEvent(); + frame.setFrame(path); + doEvent.invoke_(frame); + animation.addEvent(frame); + } + updateEventList.invoke_(); + } + }); + + JButton eventView = (JButton) contextEditor.find("view-events"); + eventView.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + final SwingEngine engine = new SwingEngine("animator/view-events.xml"); + // final JDialog dialog = new JDialog("Event viewer"); + final JDialog dialog = new JDialog(); + + final JButton close = (JButton) engine.find("close"); + close.addActionListener( new AbstractAction(){ + public void actionPerformed(ActionEvent event ){ + dialog.setVisible(false); + } + }); + + final JPanel work = (JPanel) engine.find("work"); + + final Lambda1 update = new Lambda1(){ + /* multiple requests should really just be folded + * into a single call to this function. + */ + private synchronized void doit(){ + int x = 0; + int y = 0; + work.removeAll(); + double maxWidth = Math.sqrt(animation.getEvents().size()); + for (AnimationEvent aevent : animation.getEvents()){ + JPanel editor = aevent.getEditor(animation, area); + // editor.setSize(50, 50); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.gridx = x; + constraints.gridy = y; + constraints.weightx = 0.1; + constraints.weighty = 0.1; + constraints.fill = GridBagConstraints.BOTH; + // constraints.anchor = GridBagConstraints.NORTHWEST; + work.add(editor, constraints); + x += 1; + if (x > maxWidth){ + y += 1; + x = 0; + } + } + work.revalidate(); + } + + public Object invoke(Object o){ + SwingUtilities.invokeLater(new Runnable(){ + public void run(){ + doit(); + } + }); + return null; + } + }; + + update.invoke_(null); + animation.addChangeUpdate(update); + + dialog.getContentPane().add((JPanel) engine.getRootComponent()); + dialog.setSize(300, 300); + dialog.setVisible(true); + } + }); + + JButton eventAdd = (JButton) contextEditor.find( "add-event" ); + eventAdd.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + AnimationEvent temp = EventFactory.getEvent((String)eventSelect.getSelectedItem()); + doEvent.invoke_(temp); + int index = 0; + if (eventList.getSelectedIndex() != -1){ + index = animation.addEvent(temp, eventList.getSelectedIndex() + 1); + } else { + index = animation.addEvent(temp); + } + updateEventList.invoke_(); + eventList.setSelectedIndex(index); + } + }); + + JButton eventEdit = (JButton) contextEditor.find( "edit-event" ); + eventEdit.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + if( ! animation.getEvents().isEmpty()){ + AnimationEvent temp = (AnimationEvent) animation.getEvents().elementAt( eventList.getSelectedIndex() ); + doEvent.invoke_(temp); + } + } + }); + + JButton eventRemove = (JButton) contextEditor.find("remove-event"); + AbstractAction doRemove = new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + if (! animation.getEvents().isEmpty()){ + animation.removeEvent(eventList.getSelectedIndex()); + updateEventList.invoke_(); + } + } + }; + + eventList.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "remove-event"); + eventList.getActionMap().put("remove-event", doRemove); + + eventRemove.addActionListener(doRemove); + + JButton eventUp = (JButton) contextEditor.find("up-event"); + eventUp.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + if (! animation.getEvents().isEmpty()){ + int index1 = eventList.getSelectedIndex()-1 < 0 ? 0 : eventList.getSelectedIndex() - 1; + int index2 = eventList.getSelectedIndex(); + animation.swapEvents(index1, index2); + updateEventList.invoke_(); + eventList.setSelectedIndex(index1); + eventList.ensureIndexIsVisible(index1); + } + } + }); + + JButton eventDown = (JButton) contextEditor.find("down-event"); + eventDown.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + if (! animation.getEvents().isEmpty()){ + int index1 = eventList.getSelectedIndex()+1 > animation.getEvents().size() ? animation.getEvents().size() : eventList.getSelectedIndex() + 1; + int index2 = eventList.getSelectedIndex(); + animation.swapEvents(index1, index2); + updateEventList.invoke_(); + eventList.setSelectedIndex(index1); + eventList.ensureIndexIsVisible(index1); + } + } + }); + + final ObjectBox eventCopy = new ObjectBox(); + + /* ctrl-c */ + eventList.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_C, 2), "copy-event"); + /* ctrl-v */ + eventList.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_V, 2), "paste-event"); + eventList.getActionMap().put("copy-event", new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + if (eventList.getSelectedIndex() != -1){ + AnimationEvent what = (AnimationEvent) animation.getEvents().elementAt(eventList.getSelectedIndex()); + eventCopy.set(what.copy()); + } + } + }); + + eventList.getActionMap().put("paste-event", new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + if (eventCopy.get() != null){ + AnimationEvent temp = ((AnimationEvent) eventCopy.get()).copy(); + int index = 0; + if (eventList.getSelectedIndex() != -1){ + index = animation.addEvent(temp, eventList.getSelectedIndex() + 1); + } else { + index = animation.addEvent(temp); + } + updateEventList.invoke_(); + eventList.setSelectedIndex(index); + } + } + }); + + return (JComponent) contextEditor.getRootComponent(); + } + + private JPanel makeOnionSkinningPanel(final Animation animation){ + final SwingEngine optionsEngine = new SwingEngine("animator/onion-options.xml"); + final JSlider skins = (JSlider) optionsEngine.find("skins"); + final JLabel many = (JLabel) optionsEngine.find("how-many"); + skins.setValue(animation.getOnionSkins()); + + skins.addChangeListener(new ChangeListener(){ + private String descriptive(int what){ + if (what < 0){ + return "Showing " + what + " past frames"; + } + if (what == 0){ + return "Showing no frames"; + } + if (what > 0){ + return "Showing " + what + " future frames"; + } + return "WTF"; + } + + public void stateChanged(ChangeEvent change){ + many.setText(descriptive(skins.getValue())); + animation.setOnionSkins(skins.getValue()); + animation.forceRedraw(); + } + }); + + final JCheckBox onionSkinning = (JCheckBox) optionsEngine.find("onion-skinning"); + onionSkinning.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + animation.setOnionSkinning(onionSkinning.isSelected()); + animation.forceRedraw(); + } + }); + + final JRadioButton front = (JRadioButton) optionsEngine.find("front"); + final JRadioButton back = (JRadioButton) optionsEngine.find("back"); + front.setActionCommand("front"); + back.setActionCommand("back"); + + AbstractAction change = new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + if (event.getActionCommand().equals("front")){ + animation.setOnionSkinFront(true); + } else { + animation.setOnionSkinFront(false); + } + + animation.forceRedraw(); + } + }; + + front.addActionListener(change); + back.addActionListener(change); + + return (JPanel) optionsEngine.getRootComponent(); + } + + private JPanel makeAdjustOffsetsPanel(final Animation animation){ + final SwingEngine optionsEngine = new SwingEngine("animator/adjust-offsets.xml"); + + class OffsetAction extends AbstractAction { + public OffsetAction(Lambda1 doOffset, Lambda1 undoOffset, String undoMessage){ + this.doOffset = doOffset; + this.undoOffset = undoOffset; + this.undoMessage = undoMessage; + } + + Lambda1 doOffset; + Lambda1 undoOffset; + String undoMessage; + + public List getOffsets(Vector events){ + List offsets = new ArrayList(); + for (AnimationEvent event: events){ + /* instanceof is justified here because we can treat + * events like an ADT + */ + if (event instanceof OffsetEvent){ + offsets.add((OffsetEvent) event); + } + } + return offsets; + } + + private void updateOffsets(Lambda1 doit){ + for (OffsetEvent offset: getOffsets(animation.getEvents())){ + try{ + doit.invoke(offset); + } catch (Exception e){ + System.out.println(e); + } + } + } + + public void actionPerformed(ActionEvent event){ + updateOffsets(doOffset); + animation.applyEvents(); + animation.forceRedraw(); + + Undo.addUndo(undoMessage, new AbstractFunction0(){ + public BoxedUnit apply(){ + updateOffsets(undoOffset); + animation.applyEvents(); + animation.forceRedraw(); + return null; + } + }); + } + }; + + JButton left = (JButton) optionsEngine.find("left"); + JButton right = (JButton) optionsEngine.find("right"); + JButton up = (JButton) optionsEngine.find("up"); + JButton down = (JButton) optionsEngine.find("down"); + + left.addActionListener(new OffsetAction(new Lambda1(){ + public Object invoke(Object o){ + OffsetEvent offset = (OffsetEvent) o; + offset.setX(offset.getX() - 1); + return null; + } + }, new Lambda1(){ + public Object invoke(Object o){ + OffsetEvent offset = (OffsetEvent) o; + offset.setX(offset.getX() + 1); + return null; + } + }, "X offset")); + + right.addActionListener(new OffsetAction(new Lambda1(){ + public Object invoke(Object o){ + OffsetEvent offset = (OffsetEvent) o; + offset.setX(offset.getX() + 1); + return null; + } + }, new Lambda1(){ + public Object invoke(Object o){ + OffsetEvent offset = (OffsetEvent) o; + offset.setX(offset.getX() - 1); + return null; + } + }, "X offset")); + + up.addActionListener(new OffsetAction(new Lambda1(){ + public Object invoke(Object o){ + OffsetEvent offset = (OffsetEvent) o; + offset.setY(offset.getY() - 1); + return null; + } + }, new Lambda1(){ + public Object invoke(Object o){ + OffsetEvent offset = (OffsetEvent) o; + offset.setY(offset.getY() - 1); + return null; + } + }, "Y offset")); + + down.addActionListener(new OffsetAction(new Lambda1(){ + public Object invoke(Object o){ + OffsetEvent offset = (OffsetEvent) o; + offset.setY(offset.getY() + 1); + return null; + } + }, new Lambda1(){ + public Object invoke(Object o){ + OffsetEvent offset = (OffsetEvent) o; + offset.setY(offset.getY() - 1); + return null; + } + }, "Y offset")); + + return (JPanel) optionsEngine.getRootComponent(); + } + + private void adjustSlider(JSlider slider, int much){ + slider.setValue(slider.getValue() + much); + } + + private boolean rightClick(MouseEvent event){ + return event.getButton() == MouseEvent.BUTTON3; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/BasicObject.java b/editor/src/main/java/com/rafkind/paintown/animator/BasicObject.java new file mode 100644 index 000000000..fdd1762bc --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/BasicObject.java @@ -0,0 +1,35 @@ +package com.rafkind.paintown.animator; + +import java.io.File; + +import com.rafkind.paintown.exception.LoadException; + +public abstract class BasicObject{ + + private File path; + + private String name = ""; + + public BasicObject( String name ){ + this.name = name; + } + + public void setPath(File f){ + path = f; + } + + public File getPath(){ + return path; + } + + public String getName(){ + return name; + } + + public void setName(String n){ + name = n; + } + + abstract public void saveData() throws LoadException; + abstract public void loadData( File f ) throws LoadException; +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/BoundingBox.java b/editor/src/main/java/com/rafkind/paintown/animator/BoundingBox.java new file mode 100644 index 000000000..87b4f0f2d --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/BoundingBox.java @@ -0,0 +1,37 @@ +package com.rafkind.paintown.animator; + +import java.util.*; +import javax.swing.*; +import java.awt.*; + +public class BoundingBox{ + public int x1; + public int y1; + public int x2; + public int y2; + + public BoundingBox( int x1, int y1, int x2, int y2){ + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + } + + + public boolean empty(){ + return x1 == 0 && y1 == 0 && x2 == 0 && y2 == 0; + } + + public void render( Graphics g, int x, int y, Color color ){ + Color oldColor = g.getColor(); + + if (x1 < x2 || y1 < y2){ + g.setColor(color); + } else { + g.setColor(new Color(192, 84, 130)); + } + + g.drawRect(x + Math.min(x1, x2), y + Math.min(y1, y2), Math.abs(x2 - x1), Math.abs(y2 - y1)); + g.setColor(oldColor); + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/CharacterAnimation.java b/editor/src/main/java/com/rafkind/paintown/animator/CharacterAnimation.java new file mode 100644 index 000000000..037583b9e --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/CharacterAnimation.java @@ -0,0 +1,469 @@ +package com.rafkind.paintown.animator; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.event.*; +import javax.swing.Timer; +import java.io.*; + +import org.swixml.SwingEngine; + +import com.rafkind.paintown.Undo; +import com.rafkind.paintown.Lambda0; +import com.rafkind.paintown.Lambda1; +import com.rafkind.paintown.Lambda2; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.exception.*; +import com.rafkind.paintown.RelativeFileChooser; +import com.rafkind.paintown.animator.DrawArea; +import com.rafkind.paintown.animator.DrawState; +import com.rafkind.paintown.animator.SpecialPanel; +import com.rafkind.paintown.animator.NewAnimator; +import com.rafkind.paintown.animator.events.AnimationEvent; +import com.rafkind.paintown.animator.events.EventFactory; +import com.rafkind.paintown.animator.events.OffsetEvent; + +import java.awt.geom.AffineTransform; + +import java.util.List; +import java.util.Vector; +import java.util.Map; +import java.util.HashMap; +import java.util.Iterator; +import java.util.ArrayList; + +import scala.runtime.AbstractFunction0; +import scala.runtime.BoxedUnit; + +public class CharacterAnimation extends JPanel { + private boolean loaded = false; + private Lambda0 loader; + private final Animation animation; + + private synchronized boolean isLoaded(){ + return loaded; + } + + private synchronized void doneLoading(){ + loaded = true; + } + + public void paint(Graphics g){ + if (!isLoaded()){ + doneLoading(); + SwingUtilities.invokeLater( + new Runnable(){ + public void run(){ + CharacterAnimation.this.loader.invoke_(); + CharacterAnimation.this.revalidate(); + } + }); + } + super.paint(g); + } + + public CharacterAnimation(final AnimatedObject object, final Animation animation, final Lambda2 changeName, final Detacher outerDetacher){ + final Detacher detacher = new Detacher(){ + public JFrame detach(JComponent object, String name){ + return outerDetacher.detach(CharacterAnimation.this, name); + } + + public void attach(JComponent object, String name){ + outerDetacher.attach(CharacterAnimation.this, name); + } + }; + + this.animation = animation; + this.loader = new Lambda0(){ + public Object invoke() throws Exception { + CharacterAnimation.this.setLayout(new GridBagLayout()); + + JPanel area = new CharacterCanvas(object, animation, changeName, detacher); + + GridBagConstraints animConstraints = new GridBagConstraints(); + + animConstraints.gridx = 0; + animConstraints.gridy = 0; + animConstraints.weightx = 1; + animConstraints.weighty = 1; + animConstraints.fill = GridBagConstraints.BOTH; + animConstraints.anchor = GridBagConstraints.NORTHWEST; + + CharacterAnimation.this.add(area, animConstraints); + return null; + } + }; + } + + public Animation getAnimation(){ + return animation; + } + + private class CharacterCanvas extends AnimationCanvas { + public CharacterCanvas(AnimatedObject object, Animation animation, Lambda2 changeName, final Detacher detacher){ + super(object, animation, changeName, detacher); + } + + protected JComponent makeProperties(final AnimatedObject object, final Animation animation, final Lambda2 changeName){ + final SwingEngine animEditor = new SwingEngine("animator/animation-properties.xml"); + + final SwingEngine contextEditor = animEditor; + final JTextField nameField = (JTextField) contextEditor.find("name"); + + nameField.setText(animation.getName()); + + nameField.getDocument().addDocumentListener(new DocumentListener(){ + final CharacterAnimation self = CharacterAnimation.this; + public void changedUpdate(DocumentEvent e){ + animation.setName(nameField.getText()); + changeName.invoke_(self, nameField.getText()); + } + + public void insertUpdate(DocumentEvent e){ + animation.setName(nameField.getText()); + changeName.invoke_(self, nameField.getText()); + } + + public void removeUpdate(DocumentEvent e){ + animation.setName(nameField.getText()); + changeName.invoke_(self, nameField.getText()); + } + }); + + final JComboBox typeCombo = (JComboBox) contextEditor.find("type"); + typeCombo.addItem("none"); + typeCombo.addItem("attack"); + typeCombo.setSelectedItem(animation.getType()); + typeCombo.addActionListener( new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + final String old = animation.getType(); + final ActionListener self = this; + Undo.addUndo("Revert animation type to " + old, + new AbstractFunction0(){ + public BoxedUnit apply(){ + animation.setType(old); + typeCombo.removeActionListener(self); + typeCombo.setSelectedItem(old); + typeCombo.addActionListener(self); + return null; + } + }); + animation.setType((String) typeCombo.getSelectedItem()); + } + }); + + final JList keyList = (JList) contextEditor.find("keys"); + final JComboBox keySelect = (JComboBox) contextEditor.find("key-select"); + + keySelect.addItem("key_idle"); + keySelect.addItem("key_up"); + keySelect.addItem("key_down"); + keySelect.addItem("key_back"); + keySelect.addItem("key_forward"); + keySelect.addItem("key_upperback"); + keySelect.addItem("key_upperforward"); + keySelect.addItem("key_downback"); + keySelect.addItem("key_downforward"); + keySelect.addItem("key_jump"); + // keySelect.addItem("key_block"); + keySelect.addItem("key_attack1"); + keySelect.addItem("key_attack2"); + keySelect.addItem("key_attack3"); + keySelect.addItem("key_attack4"); + keySelect.addItem("key_attack5"); + keySelect.addItem("key_attack6"); + + keyList.setListData(animation.getKeys()); + + JButton keyAdd = (JButton) contextEditor.find("add-key"); + keyAdd.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + final int index = animation.addKey((String) keySelect.getSelectedItem()); + keyList.setListData(animation.getKeys()); + + Undo.addUndo("Remove key " + animation.getKeys().get(index), + new AbstractFunction0(){ + public BoxedUnit apply(){ + animation.removeKey(index); + keyList.setListData(animation.getKeys()); + return null; + } + }); + } + }); + JButton keyRemove = (JButton) contextEditor.find("remove-key"); + keyRemove.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + if (! animation.getKeys().isEmpty()){ + final String key = animation.getKeys().get(keyList.getSelectedIndex()); + animation.removeKey(keyList.getSelectedIndex()); + keyList.setListData(animation.getKeys()); + + Undo.addUndo("Add key " + key, new AbstractFunction0(){ + public BoxedUnit apply(){ + animation.addKey(key); + keyList.setListData(animation.getKeys()); + return null; + } + }); + } + } + }); + JButton keyUp = (JButton) contextEditor.find("up-key"); + keyUp.addActionListener( new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + if (! animation.getKeys().isEmpty()){ + final int index1 = keyList.getSelectedIndex()-1 < 0 ? 0 : keyList.getSelectedIndex() - 1; + final int index2 = keyList.getSelectedIndex(); + final String temp1 = (String) animation.getKeys().elementAt(index1); + final String temp2 = (String) animation.getKeys().elementAt(index2); + + animation.getKeys().setElementAt(temp1,index2); + animation.getKeys().setElementAt(temp2,index1); + keyList.setListData(animation.getKeys()); + keyList.setSelectedIndex(index1); + + Undo.addUndo("Move key down", new AbstractFunction0(){ + public BoxedUnit apply(){ + /* FIXME: if the keys were deleted then this will break */ + animation.getKeys().setElementAt(temp1, index1); + animation.getKeys().setElementAt(temp2, index2); + keyList.setListData(animation.getKeys()); + return null; + } + }); + } + } + }); + + JButton keyDown = (JButton) contextEditor.find("down-key"); + keyDown.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event ){ + if (! animation.getKeys().isEmpty()){ + final int index1 = keyList.getSelectedIndex() + 1 > animation.getKeys().size() ? animation.getKeys().size() : keyList.getSelectedIndex() + 1; + final int index2 = keyList.getSelectedIndex(); + final String temp1 = (String) animation.getKeys().elementAt(index1); + final String temp2 = (String) animation.getKeys().elementAt(index2); + + animation.getKeys().setElementAt(temp1,index2); + animation.getKeys().setElementAt(temp2,index1); + keyList.setListData(animation.getKeys()); + keyList.setSelectedIndex(index1); + + Undo.addUndo("Move key up", new AbstractFunction0(){ + public BoxedUnit apply(){ + /* FIXME: if the keys were deleted then this will break */ + animation.getKeys().setElementAt(temp1, index1); + animation.getKeys().setElementAt(temp2, index2); + keyList.setListData(animation.getKeys()); + return null; + } + }); + + } + } + }); + + final JSpinner rangeSpinner = (JSpinner) contextEditor.find("range"); + rangeSpinner.setValue(new Integer(animation.getRange())); + rangeSpinner.addChangeListener(new ChangeListener(){ + public void stateChanged(ChangeEvent changeEvent){ + final int range = animation.getRange(); + final ChangeListener self = this; + Undo.addUndo("Set range to " + range, new AbstractFunction0(){ + public BoxedUnit apply(){ + animation.setRange(range); + animation.forceRedraw(); + rangeSpinner.removeChangeListener(self); + rangeSpinner.setValue(new Integer(range)); + rangeSpinner.addChangeListener(self); + return null; + } + }); + + animation.setRange(((Integer)rangeSpinner.getValue()).intValue()); + animation.forceRedraw(); + } + }); + + class SequenceModel implements ComboBoxModel { + private List updates; + private List animations; + private Object selected; + + public SequenceModel(){ + updates = new ArrayList(); + animations = getAnimations( object ); + selected = null; + + object.addAnimationUpdate( new Lambda1(){ + public Object invoke(Object o){ + /* FIXME: is this an animated object? */ + if (o instanceof CharacterStats){ + CharacterStats who = (CharacterStats) o; + animations = new ArrayList(); + animations = getAnimations(who); + + updateAll(); + } + return null; + } + }); + } + + private List getAnimations( AnimatedObject who ){ + List all = new ArrayList(); + Animation none = new Animation(); + none.setName( "none" ); + all.add( none ); + all.addAll( who.getAnimations() ); + for ( Iterator it = all.iterator(); it.hasNext(); ){ + Animation updateAnimation = (Animation) it.next(); + updateAnimation.addChangeUpdate( new Lambda1(){ + public Object invoke( Object a ){ + Animation ani = (Animation) a; + int index = animations.indexOf( ani ); + if ( index != -1 ){ + ListDataEvent event = new ListDataEvent( this, ListDataEvent.CONTENTS_CHANGED, index, index ); + for ( Iterator it = updates.iterator(); it.hasNext(); ){ + ListDataListener l = (ListDataListener) it.next(); + l.contentsChanged( event ); + } + } + return null; + } + }); + } + return all; + } + + public void setSelectedItem( Object item ){ + selected = item; + updateAll(); + } + + public Object getSelectedItem(){ + return selected; + } + + /* something changed.. notify listeners */ + private void updateAll(){ + ListDataEvent event = new ListDataEvent( this, ListDataEvent.CONTENTS_CHANGED, 0, 999999 ); + for ( Iterator it = updates.iterator(); it.hasNext(); ){ + ListDataListener l = (ListDataListener) it.next(); + l.contentsChanged( event ); + } + } + + public void addListDataListener( ListDataListener l ){ + updates.add( l ); + } + + public void removeListDataListener( ListDataListener l ){ + updates.remove( l ); + } + + public Object getElementAt( int index ){ + return ((Animation) animations.get( index )).getName(); + } + + public int getSize(){ + return animations.size(); + } + } + final JComboBox sequence = (JComboBox) contextEditor.find("sequence"); + sequence.setModel( new SequenceModel() ); + /* + sequence.getModel().addListDataListener( new ListDataListener(){ + public void contentsChanged( ListDataEvent e ){ + int i = sequence.getSelectedIndex(); + + if ( i > sequence.getModel().getSize() ){ + i = sequence.getModel().getSize() - 1; + } + + if ( i <= 0 ){ + i = 0; + } + + System.out.println( "Check " + i + " " + sequence.getItemAt( i ) + " vs " + animation.getSequence() ); + if ( ! sequence.getItemAt( i ).equals( animation.getSequence() ) ){ + animation.setSequence( (String) sequence.getItemAt( i ) ); + } + + sequence.setSelectedItem( animation.getSequence() ); + } + + public void intervalAdded( ListDataEvent e ){ + } + + public void intervalRemoved( ListDataEvent e ){ + } + }); + */ + /* + sequence.addItem( "none" ); + for ( Iterator it = character.getAnimations().iterator(); it.hasNext(); ){ + Animation ani = (Animation) it.next(); + if ( ! ani.getName().equals( animation.getName() ) ){ + sequence.addItem( ani.getName() ); + } + } + */ + sequence.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + /* TODO: add an undo action here */ + animation.setSequence((String) sequence.getSelectedItem()); + } + }); + sequence.setSelectedItem(animation.getSequence()); + + final JTextField basedirField = (JTextField) contextEditor.find("basedir"); + { + Dimension size = basedirField.getMinimumSize(); + size.setSize(9999999, size.getHeight()); + basedirField.setMaximumSize(size); + } + basedirField.setText(animation.getBaseDirectory()); + JButton basedirButton = (JButton) contextEditor.find("change-basedir"); + basedirButton.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + RelativeFileChooser chooser; + if (object.getPath() != null){ + chooser = NewAnimator.getNewFileChooser("Choose base directory", object.getPath()); + } else { + chooser = NewAnimator.getNewFileChooser("Choose base directory"); + } + int ret = chooser.open(); + if (ret == RelativeFileChooser.OK){ + + final String base = animation.getBaseDirectory(); + Undo.addUndo("Set base directory to " + base, new AbstractFunction0(){ + public BoxedUnit apply(){ + animation.setBaseDirectory(base); + basedirField.setText(base); + return null; + } + }); + + final String path = chooser.getPath(); + basedirField.setText(path); + animation.setBaseDirectory(path); + } + } + }); + + return (JComponent) animEditor.getRootComponent(); + } + } + + private void debugSwixml( SwingEngine engine ){ + Map all = engine.getIdMap(); + System.out.println( "Debugging swixml" ); + for ( Iterator it = all.entrySet().iterator(); it.hasNext(); ){ + Map.Entry entry = (Map.Entry) it.next(); + System.out.println( "Id: " + entry.getKey() + " = " + entry.getValue() ); + } + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/CharacterStats.java b/editor/src/main/java/com/rafkind/paintown/animator/CharacterStats.java new file mode 100644 index 000000000..787dba5c2 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/CharacterStats.java @@ -0,0 +1,339 @@ +package com.rafkind.paintown.animator; + +import java.util.*; +import java.io.*; +import javax.swing.*; + +import com.rafkind.paintown.*; +import com.rafkind.paintown.exception.*; + +public class CharacterStats extends AnimatedObject { + protected int health; + protected double jumpVelocity; + protected double speed; + protected int shadow; + + protected double scale = 1; + + // Sound data + protected String dieSound = ""; + protected String hitSound = ""; + protected String landed = ""; + + protected String intro = ""; + + // Bitmaps + protected String icon = ""; + + // Original Map for remaps + protected String origMap = ""; + + // Vector of strings containing filename locations + protected Vector remap = new Vector(); + + public CharacterStats(String name){ + super(name); + } + + public CharacterStats(String name, File f) throws LoadException { + super(name); + loadData(f); + } + + public int getHealth(){ + return health; + } + + public void setHealth(int h){ + health = h; + } + + public double getJumpVelocity(){ + return jumpVelocity; + } + + public void setJumpVelocity( double j ){ + jumpVelocity = j; + } + + public double getSpriteScale(){ + return scale; + } + + public void setSpriteScale(double x){ + this.scale = x; + } + + public double getSpeed(){ + return speed; + } + + public void setSpeed( double s){ + speed = s; + } + + public int getShadow(){ + return shadow; + } + + public void setShadow(int s){ + shadow = s; + } + + public String getIntro(){ + return intro; + } + + public void setIntro(String intro){ + this.intro = intro; + } + + public String getDieSound(){ + return dieSound; + } + + public void setDieSound(String d){ + dieSound = d; + } + + public String getHitSound(){ + return hitSound; + } + + public void setHitSound( String s ){ + hitSound = s; + } + + public String getLanded(){ + return landed; + } + + public void setLanded(String l){ + landed = l; + } + + public String getIcon(){ + return icon; + } + + public void setIcon(String i){ + icon = i; + } + + public void setOriginalMap(String map){ + origMap = map; + } + + public String getOriginalMap(){ + return origMap; + } + + public Vector getRemaps(){ + return remap; + } + + public int getMaxMaps(){ + return getRemaps().size(); + } + + public void addMap(String map){ + remap.addElement(map); + } + + public void removeMap( int index ){ + remap.remove( index ); + } + + public String getMap( int index ){ + return (String) remap.get( index ); + } + + public boolean removeMap(String map){ + Iterator itor = remap.iterator(); + while ( itor.hasNext() ){ + String tempMap = (String)itor.next(); + if ( tempMap.equals( map ) ){ + remap.removeElement( tempMap ); + return true; + } + } + return false; + } + + private HashMap createRemap( int index ) throws IOException { + HashMap map = new HashMap(); + if ( index >= 0 && index < getMaxMaps() ){ + MaskedImage original = MaskedImage.load( Data.getDataPath() + "/" + getOriginalMap() ); + MaskedImage alt = MaskedImage.load( Data.getDataPath() + "/" + getMap( index ) ); + for ( int x = 0; x < original.getWidth(); x++ ){ + for ( int y = 0; y < original.getHeight(); y++ ){ + int pixel1 = original.getRGB( x, y ); + int pixel2 = alt.getRGB( x, y ); + if ( pixel1 != pixel2 ){ + map.put( new Integer( pixel1 ), new Integer( pixel2 ) ); + } + } + } + } + return map; + } + + public void setMap( int map ){ + if ( map == -1 ){ + Lambda1.foreach_( new ArrayList( getAnimations() ), new Lambda1(){ + public Object invoke( Object a ){ + Animation ani = (Animation) a; + ani.setMap( null ); + return null; + } + }); + } else { + try{ + final HashMap hash = createRemap( map ); + final Lambda1 remap = new Lambda1(){ + public Object invoke( Object a ){ + Animation ani = (Animation) a; + ani.setMap( hash ); + return null; + } + }; + Lambda1.foreach_( new ArrayList( getAnimations() ), remap ); + } catch ( IOException e ){ + e.printStackTrace(); + } + } + } + + public Token getToken(){ + Token temp = new Token("character"); + temp.addToken(new Token("character")); + temp.addToken(new String[]{"name", "\"" + getName() + "\""}); + temp.addToken(new String[]{"health", Integer.toString(health)}); + temp.addToken(new String[]{"jump-velocity", Double.toString(jumpVelocity)}); + temp.addToken(new String[]{"speed", Double.toString(speed)}); + temp.addToken(new String[]{"scale", Double.toString(scale)}); + temp.addToken(new String[]{"type", "Player"}); + temp.addToken(new String[]{"shadow", Integer.toString(shadow)}); + if (!getIntro().equals("")){ + temp.addToken(new String[]{"intro", getIntro()}); + } + + if ( ! getDieSound().equals( "" ) ){ + temp.addToken(new String[]{"die-sound", getDieSound() }); + } + + if ( ! getHitSound().equals( "" ) ){ + temp.addToken(new String[]{"hit-sound", getHitSound() }); + } + + if(!landed.equals(""))temp.addToken(new String[]{"landed", landed}); + if(!icon.equals(""))temp.addToken(new String[]{"icon", icon}); + Iterator mapItor = remap.iterator(); + while(mapItor.hasNext()){ + String map = (String)mapItor.next(); + temp.addToken(new String[]{"remap", origMap, map}); + } + + Iterator animItor = getAnimations().iterator(); + while(animItor.hasNext()){ + Animation anim = (Animation)animItor.next(); + temp.addToken(anim.getToken()); + } + + return temp; + } + + public void saveData() throws LoadException { + try{ + FileOutputStream out = new FileOutputStream(getPath()); + PrintStream printer = new PrintStream(out); + printer.print(getToken().toString()); + printer.print("\n"); + out.close(); + System.out.println(getToken().toString()); + } catch (Exception e){ + System.out.println(e); + throw new LoadException("Couldn't save! Reason: " + e.getMessage()); + } + } + + public void loadData(File f) throws LoadException { + TokenReader reader = new TokenReader( f ); + Token head = reader.nextToken(); + + if ( ! head.getName().equals( "character" ) ){ + throw new LoadException( "Starting token is not 'character'" ); + } + + setPath(f); + + Token nameToken = head.findToken( "name" ); + if ( nameToken != null ){ + setName( nameToken.readString(0) ); + } + + Token healthToken = head.findToken( "health" ); + if ( healthToken != null ){ + health = healthToken.readInt(0); + } + + Token jumpToken = head.findToken( "jump-velocity" ); + if ( jumpToken != null ){ + jumpVelocity = jumpToken.readDouble(0); + } + + Token speedToken = head.findToken( "speed" ); + if ( speedToken != null ){ + speed = speedToken.readDouble(0); + } + + Token introToken = head.findToken("intro"); + if (introToken != null){ + intro = introToken.readString(0); + } + + Token scaleToken = head.findToken("scale"); + if (scaleToken != null){ + scale = scaleToken.readDouble(0); + } + + Token shadowToken = head.findToken( "shadow" ); + if ( shadowToken != null ){ + shadow = shadowToken.readInt(0); + } + + Token diesoundToken = head.findToken( "die-sound" ); + if ( diesoundToken != null ){ + setDieSound( diesoundToken.readString(0) ); + } + + Token hitsoundToken = head.findToken( "hit-sound" ); + if ( hitsoundToken != null ){ + String path = hitsoundToken.readString( 0 ); + setHitSound( path ); + } + + Token landedToken = head.findToken( "landed" ); + if ( landedToken != null ){ + landed = landedToken.readString(0); + } + + Token iconToken = head.findToken( "icon" ); + if ( iconToken != null ){ + icon = iconToken.readString(0); + } + + for ( Iterator it = head.findTokens( "remap" ).iterator(); it.hasNext(); ){ + Token t = (Token) it.next(); + origMap = t.readString(0); + remap.addElement(t.readString(1)); + } + + for ( Iterator it = head.findTokens( "anim" ).iterator(); it.hasNext(); ){ + Token t = (Token) it.next(); + Animation anim = new Animation( t ); + addAnimation( anim ); + new Thread( anim ).start(); + } + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/Detacher.java b/editor/src/main/java/com/rafkind/paintown/animator/Detacher.java new file mode 100644 index 000000000..9628c84e8 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/Detacher.java @@ -0,0 +1,9 @@ +package com.rafkind.paintown.animator; + +import javax.swing.JComponent; +import javax.swing.JFrame; + +interface Detacher { + public JFrame detach(JComponent object, String name); + public void attach(JComponent object, String name); +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/DrawArea.java b/editor/src/main/java/com/rafkind/paintown/animator/DrawArea.java new file mode 100644 index 000000000..f0d045a8d --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/DrawArea.java @@ -0,0 +1,776 @@ +package com.rafkind.paintown.animator; + +import java.util.*; +import javax.swing.*; +import java.awt.*; +import javax.swing.*; +import java.awt.image.*; +import java.awt.geom.*; +import java.awt.event.*; +import javax.swing.Timer; +import javax.swing.event.*; + +import org.swixml.SwingEngine; + +import com.rafkind.paintown.animator.*; +import com.rafkind.paintown.animator.events.AnimationEvent; +import com.rafkind.paintown.*; + +public final class DrawArea extends JComponent { + private int x = 50; + private int y = 50; + private int guideSize; + private double scale; + private boolean canMove = true; + private boolean snapToGrid = false; + /* start background as black */ + private DrawProperties.Properties drawProperties; + + private Animation currentAnimation; + + private static void drawImage(Graphics graphics, Image image, int x, int y, boolean flipX, boolean flipY){ + int width = image.getWidth(null); + int height = image.getHeight(null); + if (flipX && flipY){ + graphics.drawImage(image, x, y, x + width, y + height, width, height, 0, 0, null); + } else if (flipX){ + graphics.drawImage(image, x, y, x + width, y + height, width, 0, 0, height, null); + } else if (flipY){ + graphics.drawImage(image, x, y, x + width, y + height, 0, height, width, 0, null); + } else { + graphics.drawImage(image, x, y, null); + } + } + + private static class OverlayImage{ + public OverlayImage(){ + } + + public boolean isBehind(){ + return ! front; + } + + public boolean isFront(){ + return front; + } + + public void setFront(){ + front = true; + } + + public void setBehind(){ + front = false; + } + + public void setRelativeOffset(boolean relative){ + this.relativeOffset = relative; + } + + public boolean useRelativeOffset(){ + return this.relativeOffset; + } + + public void setRotation(int angle){ + this.angle = angle; + } + + public void setOffsetX(int x){ + offsetX = x; + } + + public int getOffsetX(){ + return offsetX; + } + + public void setOffsetY(int y){ + offsetY = y; + } + + public int getOffsetY(){ + return offsetY; + } + + public void setFlipX(boolean what){ + flipX = what; + } + + public boolean getFlipX(){ + return flipX; + } + + public void setFlipY(boolean what){ + flipY = what; + } + + public boolean getFlipY(){ + return flipY; + } + + public double getAlpha(){ + return alpha; + } + + public void setAlpha(double alpha){ + this.alpha = alpha; + } + + public void draw(Graphics2D graphics, double x, double y){ + if (image != null){ + Graphics2D translucent = (Graphics2D) graphics.create(); + AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float)alpha); + translucent.setComposite(composite); + + /* + double fx = x - image.getWidth(null) / 2 + offsetX; + double fy = y - image.getHeight(null) + offsetY; + */ + double fx = x + offsetX; + double fy = y + offsetY; + + /* 1. translate the image so the center bottom is at 0, 0 + * 2. rotate it by the angle + * 3. translate it by the draw position and its offset. + */ + translucent.translate(fx, fy); + translucent.rotate(Math.toRadians(angle)); + // translucent.translate(-image.getWidth(null) / 2, -image.getHeight(null) / 2); + translucent.translate(-image.getWidth(null) / 2, -image.getHeight(null)); + + /* draw at 0, 0 to let the transforms do the work */ + drawImage(translucent, image, 0, 0, flipX, flipY); + + /* + translucent.rotate(Math.toRadians(angle), fx, fy); + drawImage(translucent, image, (int) fx, (int) fy, flipX, flipY); + */ + + /* + int width = image.getWidth(null); + int height = image.getHeight(null); + if (flipX && flipY){ + translucent.drawImage(image, (int) fx, (int) fy, (int) fx + width, (int) fy + height, width, height, 0, 0, null); + } else if (flipX){ + translucent.drawImage(image, (int) fx, (int) fy, (int) fx + width, (int) fy + height, width, 0, 0, height, null); + } else if (flipY){ + translucent.drawImage(image, (int) fx, (int) fy, (int) fx + width, (int) fy + height, 0, height, width, 0, null); + } else { + translucent.drawImage(image, (int) fx, (int) fy, null); + } + */ + + translucent.dispose(); + } + } + + public MaskedImage image; + public boolean front = true; + int offsetX; + int offsetY; + boolean flipX = false; + boolean flipY = false; + double alpha = 1; + int angle = 0; + boolean relativeOffset = false; + } + + private OverlayImage overlayImage = new OverlayImage(); + + private static class OverlayAnimation{ + public OverlayAnimation(){ + } + + public void setAnimation(Animation what){ + this.animation = what; + } + + public void setAlpha(double level){ + this.alpha = level; + alpha = alpha; + if (alpha < 0){ + alpha = 0; + } + if (alpha > 1){ + alpha = 1; + } + } + + public double getAlpha(){ + return alpha; + } + + public void setFlipX(boolean what){ + flipX = what; + } + + public void setFlipY(boolean what){ + flipY = what; + } + + public void setOffsetX(int x){ + this.offsetX = x; + } + + public int getOffsetX(){ + return this.offsetX; + } + + public void setOffsetY(int x){ + this.offsetY = x; + } + + public int getOffsetY(){ + return this.offsetY; + } + + public void draw(Graphics graphics, int x, int y, double scale){ + if (animation != null){ + Graphics2D translucent = (Graphics2D) graphics.create(); + AlphaComposite alpha = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float)this.alpha); + translucent.setComposite(alpha); + AffineTransform flipper = new AffineTransform(); + double xscale = 1; + double yscale = 1; + if (flipX){ + xscale = -1; + } + if (flipY){ + yscale = -1; + } + + flipper.scale(xscale * scale, yscale * scale); + flipper.translate(xscale * (x + offsetX), yscale * (y + offsetY)); + + translucent.setTransform(flipper); + animation.draw(translucent, 0, 0); + translucent.dispose(); + } + } + + private Animation animation; + private double alpha = 1; + private boolean flipX = false; + private boolean flipY = false; + private int offsetX = 0; + private int offsetY = 0; + } + + private OverlayAnimation overlayAnimation = new OverlayAnimation(); + + /* true for behind, false for in front */ + private boolean overlayBehind = true; + private boolean resizedOnce = false; + + private java.util.List helpText = new ArrayList(); + private java.util.List scaleListeners = new ArrayList(); + + public DrawArea(final Lambda0 loader){ + this(new DrawProperties.Properties(), loader); + } + + public DrawArea(DrawProperties.Properties properties, final Lambda0 loader){ + setFocusable(true); + currentAnimation = null; + this.drawProperties = properties; + + scale = 1.0; + + this.addComponentListener(new ComponentAdapter(){ + public void componentResized(ComponentEvent event){ + if (resizedOnce == false){ + resizedOnce = true; + if (currentAnimation != null && currentAnimation.hasImage()){ + double width = currentAnimation.getWidth(); + double height = currentAnimation.getHeight(); + double myWidth = getWidth(); + double myHeight = getHeight(); + if (myWidth / width < myHeight / height){ + setScale(myWidth / width * 0.5); + } else { + setScale(myWidth / height * 0.5); + } + updateScaleListeners(); + } + setCenterX((int)(getWidth() / 2.0 / getScale())); + setCenterY((int)(getHeight() / 2.0 / getScale()) + 10); + } + } + }); + + this.addMouseListener(new MouseListener(){ + public void mouseClicked(MouseEvent e){ + requestFocusInWindow(); + } + + public void mousePressed(MouseEvent event){ + requestFocusInWindow(); + } + + public void mouseEntered(MouseEvent event){ + } + + public void mouseExited(MouseEvent event){ + } + + public void mouseReleased(MouseEvent event){ + } + }); + + this.addMouseMotionListener( new MouseMotionAdapter(){ + public void mouseDragged(MouseEvent event){ + requestFocusInWindow(); + if (canMove){ + if (DrawArea.this.isSnapToGrid()){ + double whereX = event.getX() / getScale(); + double whereY = event.getY() / getScale(); + setCenterX((int) closestSnapX(whereX)); + setCenterY((int) closestSnapY(whereY)); + } else { + setCenterX((int)(event.getX() / getScale())); + setCenterY((int)(event.getY() / getScale())); + } + DrawArea.this.repaint(); + } + } + }); + + class PopupHack{ + private Popup box; + private int counter; + public PopupHack(JComponent insides){ + Point here = DrawArea.this.getLocation(); + SwingUtilities.convertPointToScreen(here, DrawArea.this); + box = PopupFactory.getSharedInstance().getPopup(DrawArea.this, insides, (int) here.getX(), (int) here.getY()); + box.show(); + counter = 10; + } + + public void hide(){ + box.hide(); + } + + public boolean decrement(){ + counter -= 1; + return counter <= 0; + } + + public void pressed(){ + counter = 10; + } + } + + final PopupHack[] box = new PopupHack[1]; + + this.getInputMap().put( KeyStroke.getKeyStroke( KeyEvent.VK_Q, 0, false ), "press" ); + this.getActionMap().put( "press", new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + boolean ok = false; + synchronized(box){ + ok = box[0] == null; + } + if (ok){ + /* + System.out.println("Pressed a key"); + JButton button = new JButton("hello!"); + button.setSize(100,100); + */ + JComponent input = (JComponent) loader.invoke_(); + if (input == null){ + return; + } + synchronized(box){ + box[0] = new PopupHack(input); + } + final Timer t = new Timer(500, new ActionListener(){ + public void actionPerformed( ActionEvent event ){ + } + }); + t.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent event){ + synchronized(box){ + if ( box[0] != null && box[0].decrement() ){ + box[0].hide(); + box[0] = null; + t.stop(); + } + } + } + }); + t.start(); + } else { + synchronized(box){ + box[0].pressed(); + } + } + } + }); + + this.addKeyListener(new KeyListener(){ + private boolean isControl(KeyEvent event){ + return event.getKeyCode() == KeyEvent.VK_CONTROL; + } + + public void keyPressed(KeyEvent event){ + if (isControl(event)){ + DrawArea.this.enableSnapToGrid(); + } + } + + public void keyReleased(KeyEvent event){ + if (isControl(event)){ + DrawArea.this.disableSnapToGrid(); + } + } + + public void keyTyped(KeyEvent event){ + } + }); + + /* + this.addKeyListener(new KeyListener(){ + private Popup box; + public void keyPressed(KeyEvent e){ + if ( box == null ){ + System.out.println("Pressed a key"); + jbutton button = new jbutton("hello!"); + button.setsize(100,100); + box = popupfactory.getsharedinstance().getpopup(drawarea.this, button, getx(), gety()); + box.show(); + } + } + + public void keyReleased(KeyEvent e){ + if ( box != null ){ + System.out.println("Key released"); + box.hide(); + box = null; + } + } + + public void keyTyped(KeyEvent e){ + } + }); + */ + } + + private void updateScaleListeners(){ + for (Lambda0 update: scaleListeners){ + update.invoke_(); + } + } + + public void addScaleListener(Lambda0 update){ + scaleListeners.add(update); + } + + public void setOverlayRelativeOffset(boolean relative){ + overlayImage.setRelativeOffset(relative); + } + + public void setOverlayImageRotation(int angle){ + overlayImage.setRotation(angle); + } + + public void setOverlayImage(MaskedImage image){ + overlayImage.image = image; + } + + public void setOverlayImageFlipX(boolean what){ + overlayImage.setFlipX(what); + } + + public void setOverlayImageFlipY(boolean what){ + overlayImage.setFlipY(what); + } + + public void setOverlayImageFront(){ + overlayImage.setFront(); + } + + public void setOverlayImageBehind(){ + overlayImage.setBehind(); + } + + public void setOverlayImageAlpha(double alpha){ + overlayImage.setAlpha(alpha); + } + + public double getOverlayImageAlpha(){ + return overlayImage.getAlpha(); + } + + public void setOverlayImageOffsetX(int x){ + overlayImage.setOffsetX(x); + } + + public int getOverlayImageOffsetY(){ + return overlayImage.getOffsetY(); + } + + public int getOverlayImageOffsetX(){ + return overlayImage.getOffsetX(); + } + + public void setOverlayImageOffsetY(int y){ + overlayImage.setOffsetY(y); + } + + private double getGuideRatio(){ + if (guideSize > 0){ + return 10 * getMaxGuideSize() / guideSize; + } + return 1; + } + + public double closestSnapX(double x){ + return getGuideRatio() * Math.round(x / getGuideRatio()); + } + + public double closestSnapY(double y){ + return getGuideRatio() * Math.round(y / getGuideRatio()); + } + + public boolean isSnapToGrid(){ + return snapToGrid; + } + + public void enableSnapToGrid(){ + // System.out.println("Enable snap to grid"); + snapToGrid = true; + } + + public void disableSnapToGrid(){ + // System.out.println("Disable snap to grid"); + snapToGrid = false; + } + + public double getScale(){ + return scale; + } + + public void setScale(double x){ + if (x < 0.1){ + x = 0.1; + } + if (x > 4){ + x = 4; + } + scale = x; + repaint(); + } + + public double getMaxGuideSize(){ + return 10; + } + + public int getGuideSize(){ + return guideSize; + } + + public void setGuideSize(int size){ + guideSize = size; + } + + + /* + public Dimension getPreferredSize(){ + return new Dimension(800,600); + } + */ + + /* + public void setBackgroundColor(Color color){ + backgroundColor = color; + } + + public Color backgroundColor(){ + return backgroundColor; + } + */ + + public void setOverlayBehind(){ + overlayBehind = true; + } + + public void setOverlayFront(){ + overlayBehind = false; + } + + /* set opaqueness of the overlay image. + * 1 - opaque + * 0 - translucent + * anything in between will be partially transparent + */ + public void setOverlayAlpha(double alpha){ + overlayAnimation.setAlpha(alpha); + } + + public void setOverlayAnimationOffsetX(int x){ + overlayAnimation.setOffsetX(x); + } + + public int getOverlayAnimationOffsetX(){ + return overlayAnimation.getOffsetX(); + } + + public void setOverlayAnimationOffsetY(int y){ + overlayAnimation.setOffsetY(y); + } + + public int getOverlayAnimationOffsetY(){ + return overlayAnimation.getOffsetY(); + } + + public void setOverlayAnimationFlipX(boolean what){ + overlayAnimation.setFlipX(what); + } + + public void setOverlayAnimationFlipY(boolean what){ + overlayAnimation.setFlipY(what); + } + + public double getOverlayAlpha(){ + return overlayAnimation.getAlpha(); + } + + public void setOverlay(Animation animation){ + this.overlayAnimation.setAnimation(animation); + } + + public Color backgroundColor(){ + return drawProperties.getBackgroundColor(); + } + + private Color oppositeColor(Color what){ + /* don't bother fooling with HSB. the code here looks fine */ + return new Color(255 - what.getRed(), + 255 - what.getGreen(), + 255 - what.getBlue()); + } + + private void drawGrid(Graphics2D graphics){ + if (getGuideSize() > 0){ + graphics.setColor(oppositeColor(backgroundColor())); + for (double x = 0; x < getWidth(); x += getGuideRatio()){ + graphics.drawLine((int) x, 0, (int) x, getHeight()); + } + for (double y = 0; y < getHeight(); y += getGuideRatio()){ + graphics.drawLine(0, (int) y, getWidth(), (int) y); + } + } + } + + private void drawOverlay(Graphics2D g, int x, int y){ + overlayAnimation.draw(g, x, y, getScale()); + } + + public void addHelpText(String... lines){ + for (String line: lines){ + this.helpText.add(line); + } + } + + public void removeHelpText(){ + this.helpText = new ArrayList(); + } + + public void drawHelpText(Graphics2D graphics){ + graphics.setColor(new Color(255, 255, 255)); + int height = graphics.getFontMetrics(graphics.getFont()).getHeight(); + int y = height + 2; + for (String line: helpText){ + graphics.drawString(line, 2, y); + y += height + 2; + } + } + + public void paintComponent(Graphics g){ + Graphics2D g2d = (Graphics2D) g; + g2d.scale(getScale(), getScale()); + + g.setColor(backgroundColor()); + g.fillRect(0, 0, (int) (getWidth() / getScale()), (int) (getHeight() / getScale())); + drawGrid(g2d); + g.setColor(new Color(255, 255, 0)); + g.drawLine(0, y, (int) (getWidth() / getScale()), y); + g.drawLine(x, 0, x, (int) (getHeight() / getScale())); + + if (overlayImage.isBehind()){ + double useX = x; + double useY = y; + if (overlayImage.useRelativeOffset()){ + useX += currentAnimation.getOffsetX(); + useY += currentAnimation.getOffsetY(); + } + overlayImage.draw(g2d, useX, useY); + } + + if (overlayBehind){ + drawOverlay(g2d, x, y); + } + + if (currentAnimation != null){ + Graphics2D blah = (Graphics2D) g2d.create(); + AffineTransform transform = new AffineTransform(); + transform.scale(getScale(), getScale()); + transform.translate(x, y); + blah.setTransform(transform); + currentAnimation.draw(blah, 0, 0); + // currentAnimation.draw(g, x, y); + blah.dispose(); + } + + if (! overlayBehind){ + drawOverlay(g2d, x, y); + } + + if (overlayImage.isFront()){ + double useX = x; + double useY = y; + if (overlayImage.useRelativeOffset()){ + useX += currentAnimation.getOffsetX(); + useY += currentAnimation.getOffsetY(); + } + overlayImage.draw(g2d, useX, useY); + } + + /* Undo the scale to get back to 1x1 */ + g2d.scale(1 / getScale(), 1 / getScale()); + drawHelpText(g2d); + } + + public void setCenterX(int x){ + this.x = x; + } + + public void setCenterY(int y){ + this.y = y; + } + + public int getCenterX(){ + return x; + } + + public int getCenterY(){ + return y; + } + + public void enableMovement(){ + this.canMove = true; + } + + public void disableMovement(){ + this.canMove = false; + } + + public void animate(Animation animation){ + unanimate(); + currentAnimation = animation; + currentAnimation.addDrawable(this); + } + + public void unanimate(){ + if (currentAnimation != null){ + currentAnimation.removeDrawable(this); + } + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/DrawProperties.java b/editor/src/main/java/com/rafkind/paintown/animator/DrawProperties.java new file mode 100644 index 000000000..706a0b6da --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/DrawProperties.java @@ -0,0 +1,36 @@ +package com.rafkind.paintown.animator; + +import java.util.List; +import java.util.ArrayList; +import java.awt.Color; + +public abstract class DrawProperties{ + public static interface Listener{ + public void updateBackgroundColor(Color color); + } + + public static class Properties{ + private Color color = new Color(0, 0, 0); + private List listeners = new ArrayList<>(); + + public void setBackgroundColor(Color newColor){ + this.color = newColor; + for (Listener listener: listeners){ + listener.updateBackgroundColor(color); + } + } + + public Color getBackgroundColor(){ + return this.color; + } + + public void addListener(Listener listener){ + this.listeners.add(listener); + } + + public void removeListener(Listener listener){ + /* Implement? */ + } + } + +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/DrawState.java b/editor/src/main/java/com/rafkind/paintown/animator/DrawState.java new file mode 100644 index 000000000..c426cfe21 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/DrawState.java @@ -0,0 +1,47 @@ +package com.rafkind.paintown.animator; + +import java.util.*; +import java.io.*; + +public class DrawState +{ + private static boolean drawingEnabled; + + private static Vector currentDirectoryList; + + public static boolean isDrawEnabled() + { + return drawingEnabled; + } + + public static void setDrawEnabled(boolean d) + { + drawingEnabled = d; + } + + public static void setCurrentDirList(Vector l) + { + currentDirectoryList = l; + } + + public static void setCurrentDirList(String dir) + { + // Directory List + File file = new File(dir); + if ( file.isDirectory() ){ + currentDirectoryList = new Vector(); + File[] all = file.listFiles(); + //files.add( new File( ".." ) ); + for ( int i = 0; i < all.length; i++ ){ + if(all[i].getName().equals(".svn"))continue; + currentDirectoryList.addElement( dir.replaceAll("data/","") + all[ i ].getName().replaceAll("^./","") ); + } + } + } + + public static Vector getCurrentDirList() + { + return currentDirectoryList; + } + +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/IQueue.java b/editor/src/main/java/com/rafkind/paintown/animator/IQueue.java new file mode 100644 index 000000000..6367eaf84 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/IQueue.java @@ -0,0 +1,30 @@ +package com.rafkind.paintown.animator; + +import java.util.*; + +public class IQueue +{ + private Vector q = new Vector(); + + public IQueue() + { + // Nothing + } + + public Object pop() + { + Object item = q.get(q.size()); + q.remove(q.size()); + return item; + } + + public void push(Object item) + { + q.addElement(item); + } + + public void clear() + { + q.clear(); + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/Player.java b/editor/src/main/java/com/rafkind/paintown/animator/Player.java new file mode 100644 index 000000000..e9d02de8a --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/Player.java @@ -0,0 +1,797 @@ +package com.rafkind.paintown.animator; + +import java.util.*; +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.event.*; +import java.io.*; + +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.DataFlavor; +import javax.activation.DataHandler; + +import org.swixml.SwingEngine; +import javax.swing.filechooser.FileFilter; + +import com.rafkind.paintown.animator.CharacterStats; +import com.rafkind.paintown.animator.DrawArea; +import com.rafkind.paintown.exception.*; +import com.rafkind.paintown.*; + +public final class Player{ + + private JTextField nameField; + private SwingEngine playerEditor; + private CharacterStats character; + + private static class ObjectBox{ + public ObjectBox(){} + public synchronized void set(Data x){ obj = x; } + public synchronized Data get(){ return obj; } + private Data obj; + } + + public SpecialPanel getEditor(){ + return new SpecialPanel((JPanel)playerEditor.getRootComponent(), nameField, character ); + } + + public Player(final NewAnimator animator, final CharacterStats character){ + this.character = character; + + playerEditor = new SwingEngine("animator/base.xml"); + + final SwingEngine contextEditor = new SwingEngine("animator/context.xml"); + // final SwingEngine controlEditor = new SwingEngine("animator/controls.xml"); + + final JComponent animationPane = (JComponent) playerEditor.find("animation-pane"); + final JComponent propertiesPane = (JComponent) playerEditor.find("properties-pane"); + final JComponent comboPane = (JComponent) playerEditor.find("combo-pane"); + + final JTabbedPane all = (JTabbedPane) playerEditor.find("all"); + + all.setTitleAt(all.indexOfComponent(animationPane), "Animations"); + all.setTitleAt(all.indexOfComponent(propertiesPane), "Properties"); + all.setTitleAt(all.indexOfComponent(comboPane), "Combo Viewer"); + + //debugSwixml(playerEditor); + //debugSwixml(contextEditor); + + final JPanel context = (JPanel) playerEditor.find("context"); + final JTabbedPane animations = (JTabbedPane) playerEditor.find("animations"); + + final Lambda2 changeName = new Lambda2(){ + public Object invoke(Object self, Object name){ + animations.setTitleAt(animations.indexOfComponent((JComponent) self), (String) name); + return this; + } + }; + + final Detacher detacher = new Detacher(){ + JFrame lastFrame; + public void destroyFrame(JComponent object){ + if (lastFrame != null){ + lastFrame.setVisible(false); + lastFrame = null; + } + } + + public JFrame detach(final JComponent object, String name){ + animations.remove(object); + JFrame frame = new JFrame(name); + lastFrame = frame; + frame.setSize(600, 600); + frame.getContentPane().add(object); + frame.setVisible(true); + + frame.addWindowListener(new WindowAdapter(){ + public void windowClosing(WindowEvent event){ + destroyFrame(object); + } + }); + + return frame; + } + + public void attach(JComponent object, final String name){ + animations.add(object, name); + animations.setSelectedComponent(object); + destroyFrame(object); + } + }; + + final JButton addAnimation = (JButton) playerEditor.find("add-animation"); + addAnimation.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + Animation animation = new Animation(); + character.addAnimation(animation); + new Thread(animation).start(); + JComponent tab = new CharacterAnimation(character, animation, changeName, detacher); + animations.add("New animation", tab); + animations.setSelectedComponent(tab); + } + }); + + final JButton copyAnimation = (JButton) playerEditor.find("copy-animation"); + copyAnimation.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + CharacterAnimation current = (CharacterAnimation) animations.getSelectedComponent(); + if (current != null){ + Animation animation = new Animation(current.getAnimation()); + character.addAnimation(animation); + new Thread(animation).start(); + JComponent tab = new CharacterAnimation(character, animation, changeName, detacher); + animations.add(animation.getName(), tab); + animations.setSelectedComponent(tab); + } + } + }); + + final JButton removeAnimButton = (JButton) playerEditor.find("remove-animation"); + removeAnimButton.addActionListener( new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + if (okToRemoveAnimation(character)){ + /* TODO: if the player is unsaved then ask them if they + * really mean to remove the animation + */ + CharacterAnimation tab = (CharacterAnimation) animations.getSelectedComponent(); + if (tab != null){ + tab.getAnimation().kill(); + character.removeAnimation(tab.getAnimation()); + animations.remove(tab); + } + } + } + }); + + + for (Animation animation : character.getAnimations()){ + animations.add(animation.getName(), new CharacterAnimation(character, animation, changeName, detacher)); + } + + nameField = (JTextField) contextEditor.find("name"); + nameField.setText(character.getName()); + + nameField.getDocument().addDocumentListener(new DocumentListener(){ + public void changedUpdate(DocumentEvent e){ + character.setName( nameField.getText() ); + } + + public void insertUpdate(DocumentEvent e){ + character.setName( nameField.getText() ); + } + + public void removeUpdate(DocumentEvent e){ + character.setName( nameField.getText() ); + } + }); + + final JSpinner remap = (JSpinner) contextEditor.find( "remap" ); + + class RemapSpinnerModel implements SpinnerModel { + private java.util.List listeners; + int index; + + public RemapSpinnerModel(){ + listeners = new ArrayList(); + index = 0; + } + + public void addChangeListener( ChangeListener l ){ + listeners.add( l ); + } + + public Object getNextValue(){ + if ( index < character.getMaxMaps() ){ + return new Integer( index + 1 ); + } + return new Integer( index ); + } + + public Object getPreviousValue(){ + if ( index > 0 ){ + return new Integer( index - 1 ); + } + return new Integer( index ); + } + + public Object getValue(){ + return new Integer( index ); + } + + public void removeChangeListener( ChangeListener l ){ + listeners.remove( l ); + } + + public void setValue( Object value ){ + index = ((Integer) value).intValue(); + ChangeEvent event = new ChangeEvent( this ); + for ( Iterator it = listeners.iterator(); it.hasNext(); ){ + ChangeListener change = (ChangeListener) it.next(); + change.stateChanged( event ); + } + } + } + + remap.setModel( new RemapSpinnerModel() ); + + remap.setValue( new Integer( 0 ) ); + remap.addChangeListener( new ChangeListener(){ + public void stateChanged(ChangeEvent changeEvent){ + character.setMap( ((Integer) remap.getValue()).intValue() - 1 ); + } + }); + + final JSpinner scaleSpinner = (JSpinner) contextEditor.find("scale"); + scaleSpinner.setModel(new SpinnerNumberModel(character.getSpriteScale(), 0.001, 100, 0.1)); + scaleSpinner.setValue(new Double(character.getSpriteScale())); + scaleSpinner.addChangeListener(new ChangeListener(){ + public void stateChanged(ChangeEvent changeEvent){ + character.setSpriteScale(((Double) scaleSpinner.getValue()).doubleValue()); + } + }); + + final JSpinner healthSpinner = (JSpinner) contextEditor.find("health"); + healthSpinner.setValue(new Integer(character.getHealth())); + + healthSpinner.addChangeListener( new ChangeListener(){ + public void stateChanged(ChangeEvent changeEvent){ + character.setHealth( ((Integer)healthSpinner.getValue()).intValue() ); + } + }); + + final JPanel jumpSpinner = (JPanel) contextEditor.find( "jump-velocity" ); + + final JSpinner jumpSpinner2 = new JSpinner(new SpinnerNumberModel(0, -1000, 1000, 0.1)); + jumpSpinner2.setValue( new Double( character.getJumpVelocity() ) ); + + jumpSpinner2.addChangeListener( new ChangeListener(){ + public void stateChanged(ChangeEvent changeEvent){ + character.setJumpVelocity( ((Double)jumpSpinner2.getValue()).doubleValue() ); + } + }); + + jumpSpinner.add(jumpSpinner2); + + final JPanel speedSpinner = (JPanel) contextEditor.find( "speed" ); + + final JSpinner speedSpinner2 = new JSpinner(new SpinnerNumberModel(0, -1000, 1000, 0.1)); + speedSpinner2.setValue( new Double( character.getSpeed() ) ); + + speedSpinner.add(speedSpinner2); + + speedSpinner2.addChangeListener( new ChangeListener(){ + public void stateChanged(ChangeEvent changeEvent){ + character.setSpeed( ((Double)speedSpinner2.getValue()).doubleValue() ); + } + }); + + final JSpinner shadowSpinner = (JSpinner) contextEditor.find( "shadow" ); + shadowSpinner.setValue( new Integer( character.getShadow() ) ); + + shadowSpinner.addChangeListener( new ChangeListener(){ + public void stateChanged(ChangeEvent changeEvent){ + character.setShadow( ((Integer)shadowSpinner.getValue()).intValue() ); + } + }); + + final JTextField deathSoundField = (JTextField) contextEditor.find( "die-sound" ); + deathSoundField.setText( character.getDieSound() ); + + final JButton deathSoundButton = (JButton) contextEditor.find( "change-die-sound" ); + + deathSoundButton.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + RelativeFileChooser chooser = NewAnimator.getNewFileChooser("Choose a file"); + int ret = chooser.open(); + if ( ret == RelativeFileChooser.OK ){ + final String path = chooser.getPath(); + deathSoundField.setText( path ); + character.setDieSound( path ); + } + } + }); + + final JTextField hitSoundField = (JTextField) contextEditor.find( "hit-sound" ); + hitSoundField.setText( character.getHitSound() ); + JButton hitSoundButton = (JButton) contextEditor.find( "change-hit-sound" ); + hitSoundButton.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + RelativeFileChooser chooser = NewAnimator.getNewFileChooser("Choose a file"); + int ret = chooser.open(); + if ( ret == RelativeFileChooser.OK ){ + final String path = chooser.getPath(); + hitSoundField.setText( path ); + character.setHitSound( path ); + } + } + }); + + final JTextField landingSoundField = (JTextField) contextEditor.find( "land-sound" ); + landingSoundField.setText( character.getLanded() ); + + final JButton landingSoundButton = (JButton) contextEditor.find( "change-land-sound" ); + + landingSoundButton.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + RelativeFileChooser chooser = NewAnimator.getNewFileChooser("Choose a file"); + int ret = chooser.open(); + if ( ret == RelativeFileChooser.OK ){ + final String path = chooser.getPath(); + landingSoundField.setText( path ); + character.setLanded( path ); + } + } + }); + + final JTextField introField = (JTextField) contextEditor.find("intro"); + introField.setText(character.getIntro()); + + final JButton introButton = (JButton) contextEditor.find("change-intro"); + + introButton.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + RelativeFileChooser chooser = NewAnimator.getNewFileChooser("Choose a file"); + int ret = chooser.open(); + if ( ret == RelativeFileChooser.OK ){ + final String path = chooser.getPath(); + introField.setText(path); + character.setIntro(path); + } + } + }); + + + final JTextField iconField = (JTextField) contextEditor.find( "icon" ); + iconField.setText( character.getIcon() ); + + final JButton iconButton = (JButton) contextEditor.find( "change-icon" ); + + iconButton.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + RelativeFileChooser chooser = NewAnimator.getNewFileChooser("Choose a file"); + int ret = chooser.open(); + if ( ret == RelativeFileChooser.OK ){ + final String path = chooser.getPath(); + iconField.setText( path ); + character.setIcon( path ); + } + } + }); + + final JTextField origMapField = (JTextField) contextEditor.find("original-map"); + origMapField.setText(character.getOriginalMap()); + + final JButton origMapButton = (JButton) contextEditor.find("change-origmap"); + + origMapButton.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + RelativeFileChooser chooser = NewAnimator.getNewFileChooser("Choose a file"); + int ret = chooser.open(); + if ( ret == RelativeFileChooser.OK ){ + final String path = chooser.getPath(); + origMapField.setText( path ); + character.setOriginalMap( path ); + } + } + }); + + final JList remapList = (JList) contextEditor.find( "remaps" ); + remapList.setListData( character.getRemaps() ); + + final JButton addRemapButton = (JButton) contextEditor.find( "add-remap" ); + + addRemapButton.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + RelativeFileChooser chooser = NewAnimator.getNewFileChooser("Choose a file"); + int ret = chooser.open(); + if ( ret == RelativeFileChooser.OK ){ + final String path = chooser.getPath(); + character.addMap( path ); + remapList.setListData( character.getRemaps() ); + } + } + }); + + final JButton removeRemapButton = (JButton) contextEditor.find( "remove-remap" ); + + removeRemapButton.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + String temp = character.getMap( remapList.getSelectedIndex() ); + character.removeMap( temp ); + remapList.setListData( character.getRemaps() ); + } + }); + + context.add((JComponent) contextEditor.getRootComponent(), filledConstraints()); + + comboPane.add(makeComboPane(character), filledConstraints()); + } + + private JPanel makeComboPane(final AnimatedObject object){ + final SwingEngine context = new SwingEngine("animator/combo.xml"); + + final DrawArea area = new DrawArea(object.getDrawProperties(), new Lambda0(){ + public Object invoke(){ + return null; + } + }); + + final JPanel canvas = (JPanel) context.find("canvas"); + canvas.add(area, filledConstraints()); + + final JComboBox animations = (JComboBox) context.find("select"); + for (Animation animation: object.getAnimations()){ + animations.addItem(animation); + } + + final Vector animationSequenceData = new Vector(); + final JList animationSequence = (JList) context.find("animations"); + + final Lambda1 updateAnimations = new Lambda1(){ + public Object invoke(Object objectSelf){ + /* TODO: should we remove animations from the sequence + * that were removed from the object? probably yes.. + */ + animations.removeAllItems(); + for (Animation animation: object.getAnimations()){ + animations.addItem(animation); + } + return null; + } + }; + + object.addAnimationUpdate(updateAnimations); + + final JButton addAnimation = (JButton) context.find("add"); + final JButton removeAnimation = (JButton) context.find("remove"); + + final ObjectBox currentAnimation = new ObjectBox(); + currentAnimation.set(0); + + animationSequence.setCellRenderer(new DefaultListCellRenderer(){ + public Component getListCellRendererComponent( + JList list, + Object value, + int index, + boolean isSelected, + boolean cellHasFocus){ + + setText(((Animation)value).getName()); + setBackground(isSelected ? Color.gray : Color.white); + if (currentAnimation.get() == index){ + setForeground(Color.blue); + } else { + // setForeground(isSelected ? Color.white : Color.black); + setForeground(Color.black); + } + return this; + } + }); + + final Lambda1 nextAnimation = new Lambda1(){ + public Object invoke(Object self){ + int index = currentAnimation.get(); + if (index < animationSequenceData.size()){ + animationSequenceData.get(index).stopRunning(); + } + + index = (index + 1 + animationSequenceData.size()) % animationSequenceData.size(); + currentAnimation.set(index); + if (index < animationSequenceData.size()){ + Animation animation = animationSequenceData.get(index); + animation.startRunning(); + area.animate(animation); + animationSequence.repaint(); + } + + return null; + } + }; + + animationSequence.setDragEnabled(true); + animationSequence.setDropMode(DropMode.ON); + animationSequence.setTransferHandler(new TransferHandler(){ + private int toRemove = -1; + + public boolean canImport(TransferHandler.TransferSupport support){ + return true; + } + + protected void exportDone(JComponent source, Transferable data, int action){ + if (action == TransferHandler.MOVE){ + animationSequenceData.remove(toRemove); + animationSequence.setListData(animationSequenceData); + } + } + + public int getSourceActions(JComponent component){ + return TransferHandler.MOVE; + } + + protected Transferable createTransferable(JComponent component){ + return new Transferable(){ + final Object index = animationSequence.getSelectedValue(); + public Object getTransferData(DataFlavor flavor){ + return index; + } + + public DataFlavor[] getTransferDataFlavors(){ + try{ + DataFlavor[] flavors = new DataFlavor[1]; + flavors[0] = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType); + return flavors; + } catch (ClassNotFoundException ex){ + System.out.println(ex); + return null; + } + } + + public boolean isDataFlavorSupported(DataFlavor flavor){ + return true; + } + }; + } + + public boolean importData(TransferSupport transfer) { + if (!canImport(transfer)) { + return false; + } + + try{ + JList.DropLocation location = (JList.DropLocation) transfer.getDropLocation(); + toRemove = animationSequence.getSelectedIndex(); + int index = location.getIndex(); + if (index < toRemove){ + toRemove += 1; + } + Animation animation = (Animation) transfer.getTransferable().getTransferData(new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType)); + animationSequenceData.add(index, animation); + animationSequence.setSelectedIndex(index); + } catch (ClassNotFoundException exception){ + System.err.println(exception); + } catch (java.awt.datatransfer.UnsupportedFlavorException exception){ + System.err.println(exception); + } catch (IOException exception){ + System.err.println(exception); + } + + return true; + } + + }); + + addAnimation.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + Animation animation = (Animation) animations.getSelectedItem(); + animationSequenceData.add(animation); + animationSequence.setListData(animationSequenceData); + animation.addLoopNotifier(nextAnimation); + } + }); + + removeAnimation.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + if (animationSequence.getSelectedIndex() != -1){ + animationSequenceData.remove(animationSequence.getSelectedIndex()); + animationSequence.setListData(animationSequenceData); + } + } + }); + + final JButton play = (JButton) context.find("play"); + final JButton stop = (JButton) context.find("stop"); + + play.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + int index = currentAnimation.get(); + if (index >= animationSequenceData.size()){ + index = 0; + } + if (index < animationSequenceData.size()){ + Animation animation = animationSequenceData.get(index); + animation.startRunning(); + area.animate(animation); + } + } + }); + + stop.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + int index = currentAnimation.get(); + if (index < animationSequenceData.size()){ + Animation animation = animationSequenceData.get(index); + animation.stopRunning(); + } + } + }); + + final JButton nextFrame = (JButton) context.find("next-frame"); + final JButton previousFrame = (JButton) context.find("previous-frame"); + + nextFrame.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + if (currentAnimation.get() < animationSequenceData.size()){ + Animation animation = animationSequenceData.get(currentAnimation.get()); + animation.nextFrame(); + animation.forceRedraw(); + } + } + }); + + previousFrame.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + if (currentAnimation.get() < animationSequenceData.size()){ + Animation animation = animationSequenceData.get(currentAnimation.get()); + animation.previousFrame(); + animation.forceRedraw(); + } + } + }); + + + final JComboBox tools = (JComboBox) context.find("tools"); + final JPanel toolPane = (JPanel) context.find("tool-area"); + + final String chooseNone = "None"; + final String chooseBackground = "Background Color"; + final String chooseGrid = "Grid"; + final String chooseSpeedAndScale = "Speed and Scale"; + + tools.addItem(chooseNone); + tools.addItem(chooseSpeedAndScale); + tools.addItem(chooseBackground); + tools.addItem(chooseGrid); + + tools.addActionListener(new AbstractAction(){ + /* TODO: If there are too many tools then create them lazily so we + * don't spend too much time creating the animation pane on startup. + */ + final JPanel toolNone = new JPanel(); + final JPanel toolBackground = Tools.makeBackgroundTool(object, area); + final JPanel toolGrid = Tools.makeGridTool(area); + final JPanel toolSpeedAndScale = makeSpeedAndScale(animationSequenceData, area); + + private JPanel getTool(String name){ + if (name.equals(chooseNone)){ + return toolNone; + } + if (name.equals(chooseSpeedAndScale)){ + return toolSpeedAndScale; + } + if (name.equals(chooseBackground)){ + return toolBackground; + } + if (name.equals(chooseGrid)){ + return toolGrid; + } + + throw new RuntimeException("No such tool with name '" + name + "'"); + } + + public void actionPerformed(ActionEvent event){ + toolPane.removeAll(); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.gridx = 0; + constraints.gridy = 0; + constraints.weightx = 1; + constraints.weighty = 1; + constraints.fill = GridBagConstraints.NONE; + constraints.anchor = GridBagConstraints.NORTHWEST; + toolPane.add(getTool((String) tools.getSelectedItem()), constraints); + toolPane.revalidate(); + } + }); + + return (JPanel) context.getRootComponent(); + } + + private JPanel makeSpeedAndScale(final Vector animations, final DrawArea area){ + final SwingEngine context = new SwingEngine("animator/tool-speed-scale.xml"); + final JLabel animationSpeed = (JLabel) context.find("speed-num"); + final JSlider speed = (JSlider) context.find("speed"); + final double speedNumerator = 20.0; + if (animations.size() > 0){ + animationSpeed.setText("Animation speed: " + (int)(100 * animations.get(0).getAnimationSpeed()) + "%"); + speed.setValue((int) (speedNumerator / animations.get(0).getAnimationSpeed())); + } else { + speed.setValue(20); + animationSpeed.setText("Animation speed: 100%"); + } + speed.addChangeListener( new ChangeListener(){ + public void stateChanged(ChangeEvent e){ + for (Animation animation: animations){ + animation.setAnimationSpeed(speedNumerator / speed.getValue()); + } + animationSpeed.setText("Animation speed: " + (int)(100 * speed.getValue() / speedNumerator) + "%"); + } + }); + + final JButton speedIncrease = (JButton) context.find("speed:increase"); + final JButton speedDecrease = (JButton) context.find("speed:decrease"); + + speedIncrease.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + adjustSlider(speed, +1); + } + }); + + speedDecrease.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + adjustSlider(speed, -1); + } + }); + + // controls.add((JComponent)controlEditor.getRootComponent()); + + final JLabel scaleNum = (JLabel) context.find( "scale-num" ); + scaleNum.setText("Scale: " + (int)(100 * area.getScale()) + "%"); + final JSlider scale = (JSlider) context.find( "scale" ); + scale.setValue( (int)(area.getScale() * 5.0) ); + scale.addChangeListener( new ChangeListener(){ + public void stateChanged( ChangeEvent e ){ + area.setScale(scale.getValue() / 5.0); + scaleNum.setText("Scale: " + (int)(100 * area.getScale()) + "%"); + } + }); + + final JButton scaleIncrease = (JButton) context.find("scale:increase"); + final JButton scaleDecrease = (JButton) context.find("scale:decrease"); + + scaleIncrease.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + adjustSlider(scale, +1); + } + }); + + scaleDecrease.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + adjustSlider(scale, -1); + } + }); + + return (JPanel) context.getRootComponent(); + } + + private GridBagConstraints filledConstraints(){ + GridBagConstraints constraints = new GridBagConstraints(); + constraints.gridx = 0; + constraints.gridy = 0; + constraints.weightx = 1; + constraints.weighty = 1; + constraints.fill = GridBagConstraints.BOTH; + constraints.anchor = GridBagConstraints.NORTHWEST; + return constraints; + } + + private boolean okToRemoveAnimation(CharacterStats character){ + return !isUnsaved(character) || + (isUnsaved(character) && acceptRemoval()); + } + + /* true if the character has any unsaved changes */ + private boolean isUnsaved(CharacterStats character){ + /* TODO */ + return false; + } + + /* popup a dialog box asking if the user really wants to do this */ + private boolean acceptRemoval(){ + /* TODO */ + return false; + } + + private void debugSwixml( SwingEngine engine ){ + Map all = engine.getIdMap(); + System.out.println("Debugging swixml"); + for ( Iterator it = all.entrySet().iterator(); it.hasNext(); ){ + Map.Entry entry = (Map.Entry) it.next(); + System.out.println( "Id: " + entry.getKey() + " = " + entry.getValue() ); + } + } + + /* TODO: move this to a utility class */ + private void adjustSlider(JSlider slider, int much){ + slider.setValue(slider.getValue() + much); + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/Projectile.java b/editor/src/main/java/com/rafkind/paintown/animator/Projectile.java new file mode 100644 index 000000000..90f6baed4 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/Projectile.java @@ -0,0 +1,109 @@ +package com.rafkind.paintown.animator; + +import java.io.File; +import java.io.PrintStream; +import java.io.FileOutputStream; + +import java.util.Iterator; +import java.util.ArrayList; + +import com.rafkind.paintown.Lambda1; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.TokenReader; + +import com.rafkind.paintown.exception.LoadException; + +public class Projectile extends AnimatedObject { + + private Animation main; + private Animation death; + + public Projectile(String name){ + super(name); + + main = new Animation( "main" ); + new Thread(main).start(); + death = new Animation( "death" ); + new Thread(death).start(); + + addAnimation(main); + addAnimation(death); + } + + public Projectile(String name, File f) throws LoadException { + super(name); + loadData(f); + } + + public Animation getMain(){ + return main; + } + + public void setMap( int map ){ + Lambda1.foreach_( new ArrayList( getAnimations() ), new Lambda1(){ + public Object invoke( Object a ){ + Animation ani = (Animation) a; + ani.setMap( null ); + return null; + } + }); + } + + public Animation getDeath(){ + return death; + } + + public void saveData() throws LoadException { + try{ + FileOutputStream out = new FileOutputStream(getPath()); + PrintStream printer = new PrintStream(out); + printer.print(getToken().toString()); + printer.print("\n"); + out.close(); + System.out.println(getToken().toString()); + } catch ( Exception e ){ + throw new LoadException( "Couldn't save!" ); + } + } + + public void loadData( File f ) throws LoadException { + TokenReader reader = new TokenReader( f ); + Token head = reader.nextToken(); + + if ( ! head.getName().equals( "projectile" ) ){ + throw new LoadException( "Starting token is not 'projectile'" ); + } + + for ( Iterator it = head.findTokens( "anim" ).iterator(); it.hasNext(); ){ + Token animation = (Token) it.next(); + Token name = animation.findToken( "name" ); + if ( name != null ){ + if ( name.readString( 0 ).equals( "main" ) ){ + main = new Animation( animation ); + addAnimation(main); + new Thread(main).start(); + } else if ( name.readString( 0 ).equals( "death" ) ){ + death = new Animation( animation ); + addAnimation(death); + new Thread(death).start(); + } else { + System.out.println("Unknown animation"); + System.out.println(animation.toString()); + } + } + } + } + + public Token getToken(){ + Token temp = new Token("projectile"); + temp.addToken(new Token("projectile")); + + Iterator animItor = getAnimations().iterator(); + while(animItor.hasNext()){ + Animation anim = (Animation)animItor.next(); + temp.addToken(anim.getToken()); + } + + return temp; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/ProjectilePane.java b/editor/src/main/java/com/rafkind/paintown/animator/ProjectilePane.java new file mode 100644 index 000000000..a815f894c --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/ProjectilePane.java @@ -0,0 +1,88 @@ +package com.rafkind.paintown.animator; + +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import java.awt.*; + +import com.rafkind.paintown.Lambda0; +import com.rafkind.paintown.Lambda1; +import com.rafkind.paintown.Lambda2; + +import com.rafkind.paintown.RelativeFileChooser; + +import org.swixml.SwingEngine; + +public class ProjectilePane { + + private JPanel mainPanel; + private Projectile projectile; + + private Animation currentAnimation; + + public ProjectilePane(final NewAnimator animator, final Projectile projectile){ + this.projectile = projectile; + + SwingEngine engine = new SwingEngine("animator/projectile.xml"); + + final JTabbedPane tabs = (JTabbedPane) engine.find("tabs"); + + final Detacher detacher = new Detacher(){ + public JFrame detach(JComponent object, String name){ + /* TODO */ + return null; + } + + public void attach(JComponent object, String name){ + /* TODO */ + } + }; + + for (Animation animation: projectile.getAnimations()){ + tabs.add(new ProjectileCanvas(projectile, animation, detacher), animation.getName()); + } + + mainPanel = (JPanel) engine.getRootComponent(); + } + + private class ProjectileCanvas extends AnimationCanvas { + ProjectileCanvas(Projectile projectile, Animation animation, Detacher detacher){ + super(projectile, animation, new Lambda2(){ + public Object invoke(Object o1, Object o2){ + return null; + } + }, detacher); + } + + protected JComponent makeProperties(final AnimatedObject object, final Animation animation, final Lambda2 changeName){ + + final SwingEngine contextEditor = new SwingEngine("animator/projectile-properties.xml"); + + final JTextField basedirField = (JTextField) contextEditor.find("basedir"); + { + Dimension size = basedirField.getMinimumSize(); + size.setSize(9999999, size.getHeight()); + basedirField.setMaximumSize(size); + } + basedirField.setText(animation.getBaseDirectory()); + JButton basedirButton = (JButton) contextEditor.find("change-basedir"); + basedirButton.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + RelativeFileChooser chooser = NewAnimator.getNewFileChooser("Choose a base directory for frames", object.getPath()); + int ret = chooser.open(); + if (ret == RelativeFileChooser.OK){ + final String path = chooser.getPath(); + basedirField.setText(path); + animation.setBaseDirectory(path); + } + } + }); + + return (JComponent) contextEditor.getRootComponent(); + } + } + + public SpecialPanel getEditor(){ + return new SpecialPanel(mainPanel, null, projectile); + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/SpecialPanel.java b/editor/src/main/java/com/rafkind/paintown/animator/SpecialPanel.java new file mode 100644 index 000000000..3a9536417 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/SpecialPanel.java @@ -0,0 +1,39 @@ +package com.rafkind.paintown.animator; + +import java.util.*; +import java.awt.*; +import javax.swing.*; + +public class SpecialPanel extends JPanel { + protected BasicObject object; + protected JPanel child; + protected JTextField text; + + public SpecialPanel(JPanel c, JTextField t, BasicObject object ){ + this.setLayout(new GridBagLayout()); + child = c; + text = t; + GridBagConstraints constraints = new GridBagConstraints(); + constraints.gridx = 0; + constraints.gridy = 0; + constraints.weightx = 1; + constraints.weighty = 1; + constraints.fill = GridBagConstraints.BOTH; + constraints.anchor = GridBagConstraints.NORTHWEST; + // JButton button = new JButton("Hello world!"); + // layout.setConstraints(child, constraints); + // layout.setConstraints(button, constraints); + // add(new JButton("Hello world!"), constraints); + add(child, constraints); + this.object = object; + } + + /* used to update the panel name */ + public JTextField getTextBox(){ + return text; + } + + public BasicObject getObject(){ + return object; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/events/AnimationEvent.java b/editor/src/main/java/com/rafkind/paintown/animator/events/AnimationEvent.java new file mode 100644 index 000000000..1844b60ba --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/events/AnimationEvent.java @@ -0,0 +1,37 @@ +package com.rafkind.paintown.animator.events; + +import java.util.*; +import java.awt.*; +import javax.swing.*; +import com.rafkind.paintown.animator.Animation; +import com.rafkind.paintown.animator.DrawArea; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.Lambda0; + +public interface AnimationEvent{ + /* update an animation with the event semantics */ + public void interact(Animation animation); + + /* name of the event */ + public String getName(); + + /* a dialog widget that has buttons for manipulating this event */ + public JPanel getEditor(Animation animation, DrawArea area); + + /* serialize this event to an s-expression */ + public Token getToken(); + + /* set up the properties of this event given the serialized form */ + public void loadToken(Token token); + + public void addUpdateListener(Lambda0 update); + + /* destroy any data that is kept in the animation */ + public void destroy(); + + /* get a copy of this object */ + public AnimationEvent copy(); + + /* get a description of what this event does */ + public String getDescription(); +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/events/BBoxEvent.java b/editor/src/main/java/com/rafkind/paintown/animator/events/BBoxEvent.java new file mode 100644 index 000000000..3c7cd527c --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/events/BBoxEvent.java @@ -0,0 +1,113 @@ +package com.rafkind.paintown.animator.events; + +import java.util.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import com.rafkind.paintown.animator.Animation; +import com.rafkind.paintown.animator.DrawArea; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.animator.events.AnimationEvent; +import org.swixml.SwingEngine; + +import com.rafkind.paintown.animator.events.scala.AnimationEventNotifier; + +public class BBoxEvent extends AnimationEventNotifier implements AnimationEvent { + private static class Box{ + public Box(int x1, int y1, int x2, int y2){ + this.x1 = x1; + this.x2 = x2; + this.y1 = y1; + this.y2 = y2; + } + + public Box(){ + this(0,0,0,0); + } + + public int x1; + public int y1; + public int x2; + public int y2; + } + + private Box box = new Box(); + + public void loadToken(Token token){ + box.x1 = token.readInt(0); + box.y1 = token.readInt(1); + box.x2 = token.readInt(2); + box.y2 = token.readInt(3); + } + + public void interact(Animation animation){ + //area.setAttack(new BoundingBox(_x1,_y1,_x2,_y2)); + } + + public AnimationEvent copy(){ + return null; + } + + public String getName(){ + return getToken().toString(); + } + + public JPanel getEditor(final Animation animation, final DrawArea area){ + SwingEngine engine = new SwingEngine( "animator/eventbbox.xml" ); + ((JPanel)engine.getRootComponent()).setSize(200,150); + + final JSpinner x1spin = (JSpinner) engine.find( "x1" ); + x1spin.setValue(new Integer(box.x1)); + x1spin.addChangeListener( new ChangeListener(){ + public void stateChanged(ChangeEvent changeEvent){ + box.x1 = ((Integer)x1spin.getValue()).intValue(); + updateListeners(); + } + }); + final JSpinner y1spin = (JSpinner) engine.find( "y1" ); + y1spin.setValue(new Integer(box.y1)); + y1spin.addChangeListener( new ChangeListener(){ + public void stateChanged(ChangeEvent changeEvent){ + box.y1 = ((Integer)y1spin.getValue()).intValue(); + updateListeners(); + } + }); + final JSpinner x2spin = (JSpinner) engine.find( "x2" ); + x2spin.setValue(new Integer(box.x2)); + x2spin.addChangeListener( new ChangeListener(){ + public void stateChanged(ChangeEvent changeEvent){ + box.x2 = ((Integer)x2spin.getValue()).intValue(); + updateListeners(); + } + }); + final JSpinner y2spin = (JSpinner) engine.find( "y2" ); + y2spin.setValue(new Integer(box.y2)); + y2spin.addChangeListener( new ChangeListener(){ + public void stateChanged(ChangeEvent changeEvent){ + box.y2 = ((Integer)y2spin.getValue()).intValue(); + updateListeners(); + } + }); + + return (JPanel)engine.getRootComponent(); + } + + public Token getToken(){ + Token temp = new Token("bbox"); + temp.addToken(new Token("bbox")); + temp.addToken(new Token(Integer.toString(box.x1))); + temp.addToken(new Token(Integer.toString(box.y1))); + temp.addToken(new Token(Integer.toString(box.x2))); + temp.addToken(new Token(Integer.toString(box.y2))); + + return temp; + } + + public void destroy(){ + } + + public String getDescription(){ + return "Not used"; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/events/CoordsEvent.java b/editor/src/main/java/com/rafkind/paintown/animator/events/CoordsEvent.java new file mode 100644 index 000000000..846d7a143 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/events/CoordsEvent.java @@ -0,0 +1,94 @@ +package com.rafkind.paintown.animator.events; + +import java.util.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import com.rafkind.paintown.animator.Animation; +import com.rafkind.paintown.animator.DrawArea; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.animator.events.AnimationEvent; +import org.swixml.SwingEngine; + +import com.rafkind.paintown.animator.events.scala.AnimationEventNotifier; + +public class CoordsEvent extends AnimationEventNotifier implements AnimationEvent +{ + private int _x; + private int _y; + private int _z; + + public void loadToken(Token token) + { + _x = token.readInt(0); + _y = token.readInt(1); + _z = token.readInt(2); + } + + public void interact( Animation animation ){ + + } + + public AnimationEvent copy(){ + return null; + } + + public String getName() + { + return getToken().toString(); + } + + public JPanel getEditor(final Animation animation, final DrawArea area){ + SwingEngine engine = new SwingEngine( "animator/eventcoords.xml" ); + ((JPanel)engine.getRootComponent()).setSize(200,100); + + final JSpinner xspin = (JSpinner) engine.find( "x" ); + xspin.setValue(new Integer(_x)); + xspin.addChangeListener( new ChangeListener(){ + public void stateChanged(ChangeEvent changeEvent){ + _x = ((Integer)xspin.getValue()).intValue(); + updateListeners(); + } + }); + final JSpinner yspin = (JSpinner) engine.find( "y" ); + yspin.setValue(new Integer(_y)); + yspin.addChangeListener( new ChangeListener() + { + public void stateChanged(ChangeEvent changeEvent) + { + _y = ((Integer)yspin.getValue()).intValue(); + updateListeners(); + } + }); + final JSpinner zspin = (JSpinner) engine.find( "z" ); + zspin.setValue(new Integer(_z)); + zspin.addChangeListener( new ChangeListener() + { + public void stateChanged(ChangeEvent changeEvent) + { + _z = ((Integer)zspin.getValue()).intValue(); + updateListeners(); + } + }); + return (JPanel)engine.getRootComponent(); + } + + public Token getToken() + { + Token temp = new Token("coords"); + temp.addToken(new Token("coords")); + temp.addToken(new Token(Integer.toString(_x))); + temp.addToken(new Token(Integer.toString(_y))); + temp.addToken(new Token(Integer.toString(_z))); + + return temp; + } + + public void destroy(){ + } + + public String getDescription(){ + return "Not used"; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/events/DelayEvent.java b/editor/src/main/java/com/rafkind/paintown/animator/events/DelayEvent.java new file mode 100644 index 000000000..fa9ab6774 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/events/DelayEvent.java @@ -0,0 +1,132 @@ +package com.rafkind.paintown.animator.events; + +import java.util.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import com.rafkind.paintown.animator.Animation; +import com.rafkind.paintown.animator.NewAnimator; +import com.rafkind.paintown.animator.DrawArea; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.animator.events.AnimationEvent; +import org.swixml.SwingEngine; + +import com.rafkind.paintown.animator.events.scala.AnimationEventNotifier; + +public class DelayEvent extends AnimationEventNotifier implements AnimationEvent { + /* Measure time in either game ticks (Ticks), milliseconds, or seconds */ + private final static int Ticks = 0; + private final static int Milliseconds = 1; + private final static int Seconds = 2; + + private double delay; + private int delayType = Ticks; + + public void loadToken(Token token){ + delay = token.readDouble(0); + if (token.hasIndex(1)){ + String type = token.readString(1); + delayType = parseType(type.toLowerCase()); + } + } + + private int parseType(String type){ + if (type.equals("ticks")){ + return Ticks; + } else if (type.equals("ms") || type.equals("milliseconds")){ + return Milliseconds; + } else if (type.equals("s") || type.equals("sec") || type.equals("seconds")){ + return Seconds; + } + return Ticks; + } + + private String canonicalType(){ + switch (delayType){ + case Ticks: return "ticks"; + case Milliseconds: return "ms"; + case Seconds: return "s"; + } + return "ticks"; + } + + /* Delay in milliseconds */ + private double delayAmount(){ + switch (delayType){ + case Ticks: return 1000 * delay / NewAnimator.getTicksPerSecond(); + case Milliseconds: return delay; + case Seconds: return delay * 1000; + } + return 0; + } + + public void interact(Animation animation){ + animation.setDelay(delayAmount()); + } + + public String getName(){ + return getToken().toString(); + } + + public AnimationEvent copy(){ + DelayEvent event = new DelayEvent(); + event.delay = delay; + event.delayType = delayType; + return event; + } + + public JPanel getEditor(final Animation animation, final DrawArea area){ + SwingEngine engine = new SwingEngine("animator/eventdelay.xml"); + ((JPanel)engine.getRootComponent()).setSize(200,100); + + final JSpinner delayspin = (JSpinner) engine.find("delay"); + delayspin.setModel(new SpinnerNumberModel(delay, 0, 99999, 1)); + delayspin.addChangeListener(new ChangeListener(){ + public void stateChanged(ChangeEvent changeEvent){ + delay = ((Number) delayspin.getValue()).doubleValue(); + updateListeners(); + } + }); + + final JComboBox type = (JComboBox) engine.find("kind"); + type.addItem("ticks"); + type.addItem("ms"); + type.addItem("s"); + + switch (delayType){ + case Ticks: type.setSelectedItem("ticks"); break; + case Milliseconds: type.setSelectedItem("ms"); break; + case Seconds: type.setSelectedItem("s"); break; + } + + type.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + String kind = (String) type.getSelectedItem(); + delayType = parseType(kind); + updateListeners(); + } + }); + + return (JPanel)engine.getRootComponent(); + } + + public Token getToken(){ + Token temp = new Token("delay"); + temp.addToken(new Token("delay")); + temp.addToken(new Token(Double.toString(delay))); + + /* ticks is default so leave it off */ + if (delayType != Ticks){ + temp.addToken(new Token(canonicalType())); + } + return temp; + } + + public void destroy(){ + } + + public String getDescription(){ + return "Sets the amount of time (given in terms of ticks of the game) to wait before moving on to the next event. Only certain events wait for the delay time such as 'frame' and 'nop'."; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/events/EventFactory.java b/editor/src/main/java/com/rafkind/paintown/animator/events/EventFactory.java new file mode 100644 index 000000000..0fead29f9 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/events/EventFactory.java @@ -0,0 +1,72 @@ +package com.rafkind.paintown.animator.events; + +import java.util.*; +import com.rafkind.paintown.Lambda0; + +// Must populate when an event is added + +public class EventFactory{ + private static HashMap events = new HashMap(); + private static List ignoreEvents = new ArrayList(); + + private EventFactory(){ + // Nothing + } + + public static void init(){ + events.put( "attack", new Lambda0(){public Object invoke(){return new com.rafkind.paintown.animator.events.scala.AttackEvent();}}); + events.put( "bbox", new Lambda0(){public Object invoke(){return new BBoxEvent();}}); + events.put( "coords", new Lambda0(){public Object invoke(){return new CoordsEvent();}}); + events.put( "delay", new Lambda0(){public Object invoke(){return new DelayEvent();}}); + events.put( "defense", new Lambda0(){public Object invoke(){return new com.rafkind.paintown.animator.events.scala.DefenseEvent();}}); + events.put( "effect", new Lambda0(){public Object invoke(){return new com.rafkind.paintown.animator.events.scala.EffectEvent();}}); + events.put( "face", new Lambda0(){public Object invoke(){return new FaceEvent();}}); + events.put( "frame", new Lambda0(){public Object invoke(){return new com.rafkind.paintown.animator.events.scala.FrameEvent();}}); + events.put( "hittable", new Lambda0(){public Object invoke(){return new com.rafkind.paintown.animator.events.scala.HittableEvent();}}); + events.put( "jump", new Lambda0(){public Object invoke(){return new JumpEvent();}}); + events.put( "move", new Lambda0(){public Object invoke(){return new com.rafkind.paintown.animator.events.scala.MoveEvent();}}); + events.put( "nop", new Lambda0(){public Object invoke(){return new NopEvent();}}); + events.put( "next-ticket", new Lambda0(){public Object invoke(){return new TicketEvent();}}); + events.put( "offset", new Lambda0(){public Object invoke(){return new OffsetEvent();}}); + events.put( "relative-offset", new Lambda0(){public Object invoke(){return new com.rafkind.paintown.animator.events.scala.RelativeOffsetEvent();}}); + events.put( "perpetual", new Lambda0(){public Object invoke(){return new com.rafkind.paintown.animator.events.scala.PerpetualEvent();}}); + events.put( "projectile", new Lambda0(){public Object invoke(){return new ProjectileEvent();}}); + events.put("trail", new Lambda0(){public Object invoke(){ return new TrailEvent();}}); + events.put( "shadow", new Lambda0(){public Object invoke(){return new ShadowEvent();}}); + events.put( "sound", new Lambda0(){public Object invoke(){return new SoundEvent();}}); + events.put( "user", new Lambda0(){public Object invoke(){return new UserDefinedEvent();}}); + events.put( "status", new Lambda0(){public Object invoke(){return new StatusEvent();}}); + events.put( "z-distance", new Lambda0(){public Object invoke(){return new ZDistanceEvent();}}); + + ignoreEvents.add("basedir"); + ignoreEvents.add("range"); + ignoreEvents.add("keys"); + ignoreEvents.add("type"); + ignoreEvents.add("name"); + ignoreEvents.add("loop"); + ignoreEvents.add("sequence"); + } + + static{ + init(); + } + + public static AnimationEvent getEvent(String name){ + if ( ignoreEvents.contains( name ) ){ + return null; + } + try{ + return (AnimationEvent)(((Lambda0) events.get( name )).invoke()); + } catch (Exception e) { + System.out.println( "Could not get event '" + name + "'" ); + return null; + // e.printStackTrace(); + } + } + + public static Vector getNames(){ + Object[] names = events.keySet().toArray(); + Arrays.sort(names); + return new Vector(Arrays.asList(names)); + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/events/FaceEvent.java b/editor/src/main/java/com/rafkind/paintown/animator/events/FaceEvent.java new file mode 100644 index 000000000..1f89261b5 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/events/FaceEvent.java @@ -0,0 +1,66 @@ +package com.rafkind.paintown.animator.events; + +import java.util.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import com.rafkind.paintown.animator.Animation; +import com.rafkind.paintown.animator.DrawArea; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.animator.events.AnimationEvent; +import org.swixml.SwingEngine; + +import com.rafkind.paintown.animator.events.scala.AnimationEventNotifier; + +public class FaceEvent extends AnimationEventNotifier implements AnimationEvent { + private String _face = "reverse"; + + public void loadToken(Token token){ + _face = token.readString(0); + } + + public void interact(Animation area){ + } + + public AnimationEvent copy(){ + FaceEvent event = new FaceEvent(); + event._face = _face; + return event; + } + + public String getName(){ + return getToken().toString(); + } + + public JPanel getEditor(final Animation animation, final DrawArea area){ + SwingEngine engine = new SwingEngine( "animator/eventface.xml" ); + ((JPanel)engine.getRootComponent()).setSize(200,50); + + final JComboBox facebox = (JComboBox) engine.find( "face" ); + facebox.addItem("reverse"); + + facebox.addActionListener(new ActionListener(){ + public void actionPerformed(ActionEvent actionEvent){ + _face = (String)facebox.getSelectedItem(); + updateListeners(); + } + }); + return (JPanel)engine.getRootComponent(); + } + + public Token getToken(){ + Token temp = new Token("face"); + temp.addToken(new Token("face")); + temp.addToken(new Token(_face)); + + return temp; + } + + public void destroy(){ + } + + public String getDescription(){ + return "Sets the direction the character is facing (either left or right)"; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/events/JumpEvent.java b/editor/src/main/java/com/rafkind/paintown/animator/events/JumpEvent.java new file mode 100644 index 000000000..35cb653c3 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/events/JumpEvent.java @@ -0,0 +1,95 @@ +package com.rafkind.paintown.animator.events; + +import java.util.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import com.rafkind.paintown.animator.Animation; +import com.rafkind.paintown.animator.DrawArea; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.animator.events.AnimationEvent; +import org.swixml.SwingEngine; + +import com.rafkind.paintown.animator.events.scala.AnimationEventNotifier; + +public class JumpEvent extends AnimationEventNotifier implements AnimationEvent { + private double _x; + private double _y; + private double _z; + + public void loadToken(Token token){ + _x = token.readDouble(0); + _y = token.readDouble(1); + _z = token.readDouble(2); + } + + public void interact( Animation animation ){ + } + + public AnimationEvent copy(){ + JumpEvent event = new JumpEvent(); + event._x = _x; + event._y = _y; + event._z = _z; + return event; + } + + public String getName(){ + return getToken().toString(); + } + + public JPanel getEditor(final Animation animation, final DrawArea area){ + SwingEngine engine = new SwingEngine( "animator/eventjump.xml" ); + ((JPanel)engine.getRootComponent()).setSize(200,150); + + final JPanel xpanel = (JPanel) engine.find( "x" ); + final JSpinner xspin = new JSpinner(new SpinnerNumberModel(0, -1000, 1000, .01)); + xpanel.add(xspin); + xspin.setValue(new Double(_x)); + xspin.addChangeListener( new ChangeListener(){ + public void stateChanged(ChangeEvent changeEvent){ + _x = ((Double)xspin.getValue()).doubleValue(); + updateListeners(); + } + }); + final JPanel ypanel = (JPanel) engine.find( "y" ); + final JSpinner yspin = new JSpinner(new SpinnerNumberModel(0, -1000, 1000, .01)); + ypanel.add(yspin); + yspin.setValue(new Double(_y)); + yspin.addChangeListener( new ChangeListener(){ + public void stateChanged(ChangeEvent changeEvent){ + _y = ((Double)yspin.getValue()).doubleValue(); + updateListeners(); + } + }); + final JPanel zpanel = (JPanel) engine.find( "z" ); + final JSpinner zspin = new JSpinner(new SpinnerNumberModel(0, -1000, 1000, .01)); + zpanel.add(zspin); + zspin.setValue(new Double(_z)); + zspin.addChangeListener( new ChangeListener(){ + public void stateChanged(ChangeEvent changeEvent){ + _z = ((Double)zspin.getValue()).doubleValue(); + updateListeners(); + } + }); + return (JPanel)engine.getRootComponent(); + } + + public Token getToken(){ + Token temp = new Token("jump"); + temp.addToken(new Token("jump")); + temp.addToken(new Token(Double.toString(_x))); + temp.addToken(new Token(Double.toString(_y))); + temp.addToken(new Token(Double.toString(_z))); + + return temp; + } + + public void destroy(){ + } + + public String getDescription(){ + return "Not used"; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/events/NopEvent.java b/editor/src/main/java/com/rafkind/paintown/animator/events/NopEvent.java new file mode 100644 index 000000000..a37f88068 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/events/NopEvent.java @@ -0,0 +1,50 @@ +package com.rafkind.paintown.animator.events; + +import java.util.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import com.rafkind.paintown.animator.Animation; +import com.rafkind.paintown.animator.DrawArea; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.animator.events.AnimationEvent; +import org.swixml.SwingEngine; + +import com.rafkind.paintown.animator.events.scala.AnimationEventNotifier; + +public class NopEvent extends AnimationEventNotifier implements AnimationEvent{ + public void loadToken(Token token){ + // Nothing to be done + } + + public void interact( Animation animation ){ + animation.delay(); + } + + public AnimationEvent copy(){ + return new NopEvent(); + } + + public String getName(){ + return getToken().toString(); + } + + public JPanel getEditor(final Animation animation, final DrawArea area){ + // Not necessary + return null; + } + + public Token getToken(){ + Token temp = new Token("nop"); + temp.addToken(new Token("nop")); + return temp; + } + + public void destroy(){ + } + + public String getDescription(){ + return "Does nothing but wait for the delay time to pass."; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/events/OffsetEvent.java b/editor/src/main/java/com/rafkind/paintown/animator/events/OffsetEvent.java new file mode 100644 index 000000000..20523c6f2 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/events/OffsetEvent.java @@ -0,0 +1,100 @@ +package com.rafkind.paintown.animator.events; + +import java.util.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import com.rafkind.paintown.animator.Animation; +import com.rafkind.paintown.animator.DrawArea; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.animator.events.AnimationEvent; +import org.swixml.SwingEngine; + +import com.rafkind.paintown.animator.events.scala.AnimationEventNotifier; + +public class OffsetEvent extends AnimationEventNotifier implements AnimationEvent { + private int _x; + private int _y; + + public void loadToken(Token token){ + _x = token.readInt(0); + _y = token.readInt(1); + } + + public AnimationEvent copy(){ + OffsetEvent event = new OffsetEvent(); + event._x = _x; + event._y = _y; + return event; + } + + public void interact(Animation animation){ + animation.setOffsetX(_x); + animation.setOffsetY(_y); + } + + public void setX(int x){ + _x = x; + } + + public int getX(){ + return _x; + } + + public void setY(int y){ + _y = y; + } + + public int getY(){ + return _y; + } + + public String getName(){ + return getToken().toString(); + } + + public JPanel getEditor(final Animation animation, final DrawArea area){ + SwingEngine engine = new SwingEngine("animator/eventoffset.xml"); + ((JPanel)engine.getRootComponent()).setSize(200,100); + + final JSpinner xspin = (JSpinner) engine.find("x"); + xspin.setValue(new Integer(_x)); + xspin.addChangeListener(new ChangeListener(){ + public void stateChanged(ChangeEvent changeEvent){ + _x = ((Integer)xspin.getValue()).intValue(); + updateListeners(); + interact(animation); + animation.forceRedraw(); + } + }); + + final JSpinner yspin = (JSpinner) engine.find("y"); + yspin.setValue(new Integer(_y)); + yspin.addChangeListener(new ChangeListener(){ + public void stateChanged(ChangeEvent changeEvent){ + _y = ((Integer)yspin.getValue()).intValue(); + updateListeners(); + interact(animation); + animation.forceRedraw(); + } + }); + return (JPanel)engine.getRootComponent(); + } + + public Token getToken(){ + Token temp = new Token("offset"); + temp.addToken(new Token("offset")); + temp.addToken(new Token(String.valueOf(_x))); + temp.addToken(new Token(String.valueOf(_y))); + + return temp; + } + + public void destroy(){ + } + + public String getDescription(){ + return "Moves the location of the sprite by the given x and y amounts. Use offset to make sure sprites line up within an animation."; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/events/ProjectileEvent.java b/editor/src/main/java/com/rafkind/paintown/animator/events/ProjectileEvent.java new file mode 100644 index 000000000..79da85d47 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/events/ProjectileEvent.java @@ -0,0 +1,168 @@ +package com.rafkind.paintown.animator.events; + +import java.util.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import com.rafkind.paintown.RelativeFileChooser; +import com.rafkind.paintown.animator.DrawArea; +import com.rafkind.paintown.animator.NewAnimator; +import com.rafkind.paintown.animator.Animation; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.animator.events.AnimationEvent; +import org.swixml.SwingEngine; + +import com.rafkind.paintown.animator.events.scala.AnimationEventNotifier; + +public class ProjectileEvent extends AnimationEventNotifier implements AnimationEvent +{ + private int _at_x; + private int _at_y; + private String _projectile; + private int _life; + private double _speed_x; + private double _speed_y; + + public void loadToken(Token token) + { + Token at = token.findToken("at"); + if(at != null) + { + _at_x = at.readInt(0); + _at_y = at.readInt(1); + } + Token path = token.findToken("path"); + if(path != null)_projectile = path.readString(0); + Token life = token.findToken("life"); + if(life != null)_life = life.readInt(0); + Token y2 = token.findToken("y2"); + Token speed = token.findToken("speed"); + if(speed != null) + { + _speed_x = speed.readDouble(0); + _speed_y = speed.readDouble(1); + } + } + + public void interact( Animation animation ){ + + } + + public AnimationEvent copy(){ + ProjectileEvent event = new ProjectileEvent(); + event._at_x = _at_x; + event._at_y = _at_y; + event._projectile = _projectile; + event._life = _life; + event._speed_x = _speed_x; + event._speed_y = _speed_y; + return event; + } + + public String getName() + { + return getToken().toString(); + } + + public JPanel getEditor(final Animation animation, final DrawArea area){ + final SwingEngine engine = new SwingEngine( "animator/eventprojectile.xml" ); + ((JPanel)engine.getRootComponent()).setSize(250,250); + + final JSpinner atxspin = (JSpinner) engine.find( "atx" ); + atxspin.setValue(new Integer(_at_x)); + atxspin.addChangeListener( new ChangeListener() + { + public void stateChanged(ChangeEvent changeEvent) + { + _at_x = ((Integer)atxspin.getValue()).intValue(); + updateListeners(); + } + }); + final JSpinner atyspin = (JSpinner) engine.find( "aty" ); + atyspin.setValue(new Integer(_at_y)); + atyspin.addChangeListener( new ChangeListener() + { + public void stateChanged(ChangeEvent changeEvent) + { + _at_y = ((Integer)atyspin.getValue()).intValue(); + updateListeners(); + } + }); + + final JTextField profield = (JTextField) engine.find( "projectile" ); + final JButton probutton = (JButton) engine.find( "projectile-button" ); + profield.setText(_projectile); + probutton.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + RelativeFileChooser chooser = NewAnimator.getNewFileChooser("Choose a file"); + int ret = chooser.open(); + if ( ret == RelativeFileChooser.OK ){ + final String path = chooser.getPath(); + profield.setText( path ); + _projectile = path; + updateListeners(); + } + } + }); + + + final JSpinner lifespin = (JSpinner) engine.find( "life" ); + lifespin.setValue(new Integer(_life)); + lifespin.addChangeListener( new ChangeListener() + { + public void stateChanged(ChangeEvent changeEvent) + { + _life = ((Integer)lifespin.getValue()).intValue(); + updateListeners(); + } + }); + + + final JPanel speedxpanel = (JPanel) engine.find( "speedx" ); + final JSpinner speedxspin = new JSpinner(new SpinnerNumberModel(0, -1000, 1000, .01)); + speedxpanel.add(speedxspin); + speedxspin.setValue(new Double(_speed_x)); + speedxspin.addChangeListener( new ChangeListener() + { + public void stateChanged(ChangeEvent changeEvent) + { + _speed_x = ((Double)speedxspin.getValue()).doubleValue(); + updateListeners(); + } + }); + final JPanel speedypanel = (JPanel) engine.find( "speedy" ); + final JSpinner speedyspin = new JSpinner(new SpinnerNumberModel(0, -1000, 1000, .01)); + speedypanel.add(speedyspin); + speedyspin.setValue(new Double(_speed_y)); + speedyspin.addChangeListener( new ChangeListener() + { + public void stateChanged(ChangeEvent changeEvent) + { + _speed_y = ((Double)speedyspin.getValue()).doubleValue(); + updateListeners(); + } + }); + + return (JPanel)engine.getRootComponent(); + } + + public Token getToken() + { + Token temp = new Token("projectile"); + temp.addToken(new Token("projectile")); + temp.addToken(new String[]{"at", Integer.toString(_at_x), Integer.toString(_at_y)}); + temp.addToken(new String[]{"path", _projectile}); + temp.addToken(new String[]{"life", Integer.toString(_life)}); + temp.addToken(new String[]{"speed", Double.toString(_speed_x), Double.toString(_speed_y)}); + + return temp; + } + + public void destroy(){ + } + + public String getDescription(){ + return "Creates a new projectile."; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/events/ShadowEvent.java b/editor/src/main/java/com/rafkind/paintown/animator/events/ShadowEvent.java new file mode 100644 index 000000000..212a343c6 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/events/ShadowEvent.java @@ -0,0 +1,87 @@ +package com.rafkind.paintown.animator.events; + +import java.util.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import com.rafkind.paintown.animator.Animation; +import com.rafkind.paintown.animator.DrawArea; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.animator.events.AnimationEvent; +import org.swixml.SwingEngine; + +import com.rafkind.paintown.animator.events.scala.AnimationEventNotifier; + +public class ShadowEvent extends AnimationEventNotifier implements AnimationEvent { + private int _x; + private int _y; + + public void loadToken(Token token) + { + _x = token.readInt(0); + _y = token.readInt(1); + } + + public AnimationEvent copy(){ + ShadowEvent event = new ShadowEvent(); + event._x = _x; + event._y = _y; + return event; + } + + public void interact( Animation animation ) + { + //area.setImageX(_x); + //area.setImageY(_y); + } + + public String getName() + { + return getToken().toString(); + } + + public JPanel getEditor(final Animation animation, final DrawArea area){ + SwingEngine engine = new SwingEngine( "animator/eventshadow.xml" ); + ((JPanel)engine.getRootComponent()).setSize(200,100); + + final JSpinner xspin = (JSpinner) engine.find( "x" ); + xspin.setValue(new Integer(_x)); + xspin.addChangeListener( new ChangeListener() + { + public void stateChanged(ChangeEvent changeEvent) + { + _x = ((Integer)xspin.getValue()).intValue(); + updateListeners(); + } + }); + final JSpinner yspin = (JSpinner) engine.find( "y" ); + yspin.setValue(new Integer(_y)); + yspin.addChangeListener( new ChangeListener() + { + public void stateChanged(ChangeEvent changeEvent) + { + _y = ((Integer)yspin.getValue()).intValue(); + updateListeners(); + } + }); + return (JPanel)engine.getRootComponent(); + } + + public Token getToken() + { + Token temp = new Token("shadow"); + temp.addToken(new Token("shadow")); + temp.addToken(new Token(Integer.toString(_x))); + temp.addToken(new Token(Integer.toString(_y))); + + return temp; + } + + public void destroy(){ + } + + public String getDescription(){ + return "Not used"; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/events/SoundEvent.java b/editor/src/main/java/com/rafkind/paintown/animator/events/SoundEvent.java new file mode 100644 index 000000000..d3a551fbf --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/events/SoundEvent.java @@ -0,0 +1,79 @@ +package com.rafkind.paintown.animator.events; + +import java.util.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import com.rafkind.paintown.RelativeFileChooser; +import com.rafkind.paintown.animator.DrawArea; +import com.rafkind.paintown.animator.NewAnimator; +import com.rafkind.paintown.animator.Animation; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.animator.events.AnimationEvent; +import org.swixml.SwingEngine; + +import com.rafkind.paintown.animator.events.scala.AnimationEventNotifier; + +public class SoundEvent extends AnimationEventNotifier implements AnimationEvent +{ + private String _sound; + + public void loadToken(Token token){ + _sound = token.readString(0); + } + + public void interact( Animation animation ){ + } + + public AnimationEvent copy(){ + SoundEvent event = new SoundEvent(); + + event._sound = _sound; + + return event; + } + + public String getName(){ + return getToken().toString(); + } + + public JPanel getEditor(final Animation animation, final DrawArea area){ + final SwingEngine engine = new SwingEngine( "animator/eventsound.xml" ); + ((JPanel)engine.getRootComponent()).setSize(200,50); + + final JTextField soundfield = (JTextField) engine.find( "sound" ); + final JButton soundbutton = (JButton) engine.find( "sound-button" ); + soundfield.setText(_sound); + soundbutton.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + RelativeFileChooser chooser = NewAnimator.getNewFileChooser("Choose a file"); + int ret = chooser.open(); + if ( ret == RelativeFileChooser.OK ){ + final String path = chooser.getPath(); + soundfield.setText( path ); + _sound = path; + updateListeners(); + } + } + }); + + return (JPanel)engine.getRootComponent(); + } + + public Token getToken() + { + Token temp = new Token("sound"); + temp.addToken(new Token("sound")); + temp.addToken(new Token(_sound)); + + return temp; + } + + public void destroy(){ + } + + public String getDescription(){ + return "Plays a sound from a given file."; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/events/StatusEvent.java b/editor/src/main/java/com/rafkind/paintown/animator/events/StatusEvent.java new file mode 100644 index 000000000..787c49e85 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/events/StatusEvent.java @@ -0,0 +1,71 @@ +package com.rafkind.paintown.animator.events; + +import java.util.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import com.rafkind.paintown.animator.Animation; +import com.rafkind.paintown.animator.DrawArea; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.animator.events.AnimationEvent; +import org.swixml.SwingEngine; + +import com.rafkind.paintown.animator.events.scala.AnimationEventNotifier; + +public class StatusEvent extends AnimationEventNotifier implements AnimationEvent { + private String _status = "ground"; + + public void loadToken(Token token){ + _status = token.readString(0); + } + + public void interact( Animation animation ){ + } + + public AnimationEvent copy(){ + StatusEvent event = new StatusEvent(); + event._status = _status; + return event; + } + + public String getName(){ + return getToken().toString(); + } + + public JPanel getEditor(final Animation animation, final DrawArea area){ + SwingEngine engine = new SwingEngine( "animator/eventstatus.xml" ); + ((JPanel)engine.getRootComponent()).setSize(200,50); + + final JComboBox statusbox = (JComboBox) engine.find( "status" ); + statusbox.addItem("ground"); + statusbox.addItem("jump"); + statusbox.addItem("grab"); + + statusbox.addActionListener( new ActionListener() + { + public void actionPerformed(ActionEvent actionEvent) + { + _status = (String)statusbox.getSelectedItem(); + updateListeners(); + } + }); + return (JPanel)engine.getRootComponent(); + } + + public Token getToken() + { + Token temp = new Token("status"); + temp.addToken(new Token("status")); + temp.addToken(new Token(_status)); + + return temp; + } + + public void destroy(){ + } + + public String getDescription(){ + return "Not used"; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/events/TicketEvent.java b/editor/src/main/java/com/rafkind/paintown/animator/events/TicketEvent.java new file mode 100644 index 000000000..b3f524523 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/events/TicketEvent.java @@ -0,0 +1,57 @@ +package com.rafkind.paintown.animator.events; + +import java.util.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import com.rafkind.paintown.RelativeFileChooser; +import com.rafkind.paintown.animator.NewAnimator; +import com.rafkind.paintown.animator.DrawArea; +import com.rafkind.paintown.animator.Animation; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.animator.events.AnimationEvent; +import org.swixml.SwingEngine; + +import com.rafkind.paintown.animator.events.scala.AnimationEventNotifier; + +public class TicketEvent extends AnimationEventNotifier implements AnimationEvent { + + public void loadToken(Token token){ + } + + public void interact( Animation animation ){ + } + + public AnimationEvent copy(){ + return new TicketEvent(); + } + + public String getName(){ + return getToken().toString(); + } + + public JPanel getEditor(final Animation animation, final DrawArea area){ + JPanel j = new JPanel(); + j.setSize( 200, 50 ); + return j; + /* + final SwingEngine engine = new SwingEngine( "animator/eventsound.xml" ); + ((JDialog)engine.getRootComponent()).setSize(200,50); + return (JDialog)engine.getRootComponent(); + */ + } + + public Token getToken(){ + Token temp = new Token("next-ticket"); + temp.addToken(new Token("next-ticket")); + return temp; + } + + public void destroy(){ + } + + public String getDescription(){ + return "Resets the character's attack state so that they can hit the same enemy while in the same animation. Normally when a character collides with an enemy the character is not allowed hit that same enemy while the character is in the same animation. Use 'next-ticket' if you want an attack to hit multiple times."; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/events/TrailEvent.java b/editor/src/main/java/com/rafkind/paintown/animator/events/TrailEvent.java new file mode 100644 index 000000000..2d6bd33d2 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/events/TrailEvent.java @@ -0,0 +1,89 @@ +package com.rafkind.paintown.animator.events; + +import java.util.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import com.rafkind.paintown.animator.DrawArea; +import com.rafkind.paintown.animator.Animation; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.animator.events.AnimationEvent; +import org.swixml.SwingEngine; + +import com.rafkind.paintown.animator.events.scala.AnimationEventNotifier; + +public class TrailEvent extends AnimationEventNotifier implements AnimationEvent { + private int generate; + private int length; + + public void loadToken(Token token){ + Token token_generate = token.findToken("generate"); + if (token_generate != null){ + generate = token_generate.readInt(0); + } + Token token_length = token.findToken("length"); + if (token_length != null){ + length = token_length.readInt(0); + } + } + + public AnimationEvent copy(){ + TrailEvent event = new TrailEvent(); + event.generate = generate; + event.length = length; + return event; + } + + public void interact(Animation animation){ + } + + public String getName(){ + return getToken().toString(); + } + + public JPanel getEditor(final Animation animation, final DrawArea area){ + final SwingEngine engine = new SwingEngine("animator/event-trail.xml"); + ((JPanel)engine.getRootComponent()).setSize(200,150); + + final JSpinner spin_generate = (JSpinner) engine.find("generate"); + spin_generate.setValue(new Integer(generate)); + spin_generate.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent changeEvent){ + generate = ((Integer) spin_generate.getValue()).intValue(); + interact(animation); + updateListeners(); + /* redraw when trails are working in the editor */ + // animation.forceRedraw(); + } + }); + + final JSpinner spin_length = (JSpinner) engine.find("length"); + spin_length.setValue(new Integer(length)); + spin_length.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent changeEvent){ + length = ((Integer) spin_length.getValue()).intValue(); + interact(animation); + updateListeners(); + // animation.forceRedraw(); + } + }); + + return (JPanel)engine.getRootComponent(); + } + + public Token getToken(){ + Token temp = new Token("trail"); + temp.addToken(new Token("trail")); + temp.addToken(new String[]{"generate", String.valueOf(generate)}); + temp.addToken(new String[]{"length", String.valueOf(length)}); + return temp; + } + + public void destroy(){ + } + + public String getDescription(){ + return "Creates a fading shadow that trails the player while they move. The 'generate' property sets the amount of time to wait before a new shadow is created. The 'length' property sets how long a shadow lives for."; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/events/UserDefinedEvent.java b/editor/src/main/java/com/rafkind/paintown/animator/events/UserDefinedEvent.java new file mode 100644 index 000000000..ef80c7b42 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/events/UserDefinedEvent.java @@ -0,0 +1,94 @@ +package com.rafkind.paintown.animator.events; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import org.swixml.SwingEngine; + +import com.rafkind.paintown.animator.Animation; +import com.rafkind.paintown.animator.DrawArea; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.animator.events.AnimationEvent; + +import com.rafkind.paintown.animator.events.scala.AnimationEventNotifier; + +public class UserDefinedEvent extends AnimationEventNotifier implements AnimationEvent { + private String name; + private String value; + + public UserDefinedEvent(){ + name = ""; + value = ""; + } + + public AnimationEvent copy(){ + UserDefinedEvent event = new UserDefinedEvent(); + + event.name = name; + event.value = value; + + return event; + } + + public void loadToken(Token token){ + name = token.readString(0); + value = token.readString(1); + } + + private String maybeString(String value){ + if (value.indexOf(" ") != -1){ + return "\"" + value + "\""; + } + return value; + } + + public Token getToken(){ + Token temp = new Token("user"); + temp.addToken(new Token("user")); + temp.addToken(new Token(maybeString(name))); + temp.addToken(new Token(maybeString(value))); + + return temp; + } + + public void interact(Animation animation){ + } + + public String getName(){ + return getToken().toString(); + } + + public String getDescription(){ + return "Not used"; + } + + public JPanel getEditor(final Animation animation, final DrawArea area){ + + SwingEngine engine = new SwingEngine( "animator/event-user.xml" ); + final JTextField namePanel = (JTextField) engine.find("name"); + final JTextField valuePanel = (JTextField) engine.find("value"); + + namePanel.setText(name); + valuePanel.setText(value); + namePanel.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent e){ + name = namePanel.getText(); + updateListeners(); + } + }); + + valuePanel.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent e){ + value = valuePanel.getText(); + updateListeners(); + } + }); + + return (JPanel) engine.getRootComponent(); + + } + + public void destroy(){ + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/animator/events/ZDistanceEvent.java b/editor/src/main/java/com/rafkind/paintown/animator/events/ZDistanceEvent.java new file mode 100644 index 000000000..0eb1f4050 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/animator/events/ZDistanceEvent.java @@ -0,0 +1,67 @@ +package com.rafkind.paintown.animator.events; + +import java.util.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import com.rafkind.paintown.animator.Animation; +import com.rafkind.paintown.animator.DrawArea; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.animator.events.AnimationEvent; +import org.swixml.SwingEngine; + +import com.rafkind.paintown.animator.events.scala.AnimationEventNotifier; + +public class ZDistanceEvent extends AnimationEventNotifier implements AnimationEvent { + private int _d; + + public void loadToken(Token token){ + _d = token.readInt(0); + } + + public void interact(Animation animation){ + } + + public AnimationEvent copy(){ + ZDistanceEvent event = new ZDistanceEvent(); + event._d = _d; + return event; + } + + public String getName() + { + return getToken().toString(); + } + + public JPanel getEditor(final Animation animation, final DrawArea area){ + SwingEngine engine = new SwingEngine( "animator/eventzdistance.xml" ); + ((JPanel)engine.getRootComponent()).setSize(200,50); + + final JSpinner dspin = (JSpinner) engine.find( "zdistance" ); + dspin.setValue(new Integer(_d)); + dspin.addChangeListener( new ChangeListener(){ + public void stateChanged(ChangeEvent changeEvent){ + _d = ((Integer)dspin.getValue()).intValue(); + updateListeners(); + } + }); + return (JPanel)engine.getRootComponent(); + } + + public Token getToken() + { + Token temp = new Token("z-distance"); + temp.addToken(new Token("z-distance")); + temp.addToken(new Token(Integer.toString(_d))); + + return temp; + } + + public void destroy(){ + } + + public String getDescription(){ + return "Sets the range of an attack in the Z dimension in pixels. The default range is 10 pixels."; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/exception/EditorException.java b/editor/src/main/java/com/rafkind/paintown/exception/EditorException.java new file mode 100644 index 000000000..4363b9a13 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/exception/EditorException.java @@ -0,0 +1,15 @@ +package com.rafkind.paintown.exception; + +public class EditorException extends Exception { + + public EditorException(){ + } + + public EditorException( String s ){ + super( s ); + } + + public EditorException( String s, Exception e ){ + super( s, e ); + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/exception/LoadException.java b/editor/src/main/java/com/rafkind/paintown/exception/LoadException.java new file mode 100644 index 000000000..93b0a2135 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/exception/LoadException.java @@ -0,0 +1,15 @@ +package com.rafkind.paintown.exception; + +public class LoadException extends EditorException { + + public LoadException(){ + } + + public LoadException( String s ){ + super( s ); + } + + public LoadException( String s, Exception e ){ + super( s, e ); + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/level/CatEditor.java b/editor/src/main/java/com/rafkind/paintown/level/CatEditor.java new file mode 100644 index 000000000..370aaf4ee --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/level/CatEditor.java @@ -0,0 +1,28 @@ +package com.rafkind.paintown.level; + +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; + +import java.util.Iterator; + +import com.rafkind.paintown.level.objects.Cat; +import com.rafkind.paintown.level.objects.Level; +import com.rafkind.paintown.level.objects.Block; +import com.rafkind.paintown.level.objects.Stimulation; +import com.rafkind.paintown.Lambda0; + +import org.swixml.SwingEngine; + +public class CatEditor implements PropertyEditor { + + private Cat cat; + + public CatEditor( Cat i ){ + cat = i; + } + + public JComponent createPane( final Level level, final Lambda0 closeProc ){ + return new JPanel(); + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/level/CharacterEditor.java b/editor/src/main/java/com/rafkind/paintown/level/CharacterEditor.java new file mode 100644 index 000000000..9717f90b5 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/level/CharacterEditor.java @@ -0,0 +1,185 @@ +package com.rafkind.paintown.level; + +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; + +import java.io.*; + +import java.util.Iterator; +import java.util.List; +import java.util.ArrayList; +import java.util.HashMap; + +import com.rafkind.paintown.level.objects.Character; +import com.rafkind.paintown.level.objects.Level; +import com.rafkind.paintown.level.objects.Block; +import com.rafkind.paintown.Lambda0; +import com.rafkind.paintown.Lambda1; +import com.rafkind.paintown.MinMaxSpinnerModel; + +import org.swixml.SwingEngine; + +public class CharacterEditor implements PropertyEditor { + private Character character; + + public CharacterEditor( Character i ){ + this.character = i; + } + + private int findBlock( Level level ){ + int i = 1; + for ( Iterator it = level.getBlocks().iterator(); it.hasNext(); ){ + Block b = (Block) it.next(); + if ( b.hasThing( character ) ){ + return i; + } + i += 1; + } + return i; + } + + private Block getBlock( int num, Level level ){ + return (Block) level.getBlocks().get( num - 1 ); + } + + private String convertAggressionLevel( int level ){ + + if ( level == -1 ){ + return "default"; + } + if ( level > 90 ){ + return "calm"; + } + if ( level > 80 ){ + return "unpleasant"; + } + if ( level > 70 ){ + return "mild"; + } + if ( level > 60 ){ + return "angry"; + } + if ( level > 50 ){ + return "hot headed"; + } + if ( level > 40 ){ + return "wild"; + } + if ( level > 30 ){ + return "furious"; + } + if ( level > 20 ){ + return "temper tantrum"; + } + return "insane"; + } + + + public JComponent createPane( final Level level, final Lambda0 closeProc ){ + final SwingEngine engine = new SwingEngine( "character.xml" ); + final JTextField name = (JTextField) engine.find( "name" ); + name.setText( character.getName() ); + final JTextField health = (JTextField) engine.find( "health" ); + health.setText( String.valueOf( character.getHealth() ) ); + final JTextField x = (JTextField) engine.find( "x" ); + x.setText( String.valueOf( character.getX() ) ); + final JTextField y = (JTextField) engine.find( "y" ); + y.setText( String.valueOf( character.getY() ) ); + final JTextField path = (JTextField) engine.find( "path" ); + path.setText( character.getPath() ); + path.setEditable( false ); + final JTextField aggression = (JTextField) engine.find( "aggression" ); + aggression.setText( String.valueOf( character.getAggression() ) ); + final JSpinner id = (JSpinner) engine.find("id"); + final JSpinner block = (JSpinner) engine.find( "block" ); + final JSlider aggressionScroll = (JSlider) engine.find( "aggression-slider" ); + final JLabel aggressionLevel = (JLabel) engine.find( "aggression-level" ); + final JButton boysName = (JButton) engine.find( "boys-name" ); + final JButton girlsName = (JButton) engine.find( "girls-name" ); + + boysName.addActionListener( new RandomNameAction( "boys.txt" ){ + public void actionPerformed( ActionEvent event ){ + name.setText( generateName() ); + } + }); + + girlsName.addActionListener( new RandomNameAction( "girls.txt" ){ + public void actionPerformed( ActionEvent event ){ + name.setText( generateName() ); + } + }); + + if ( character.getAggression() == -1 ){ + aggressionScroll.setValue( aggressionScroll.getMaximum() ); + } else { + aggressionScroll.setValue( character.getAggression() ); + } + + id.setValue(character.getId()); + + aggressionLevel.setText( "Aggression level: " + convertAggressionLevel( aggressionScroll.getValue() ) ); + + aggressionScroll.addChangeListener( new ChangeListener(){ + public void stateChanged( ChangeEvent e ){ + int level = aggressionScroll.getValue(); + if ( level == 100 ){ + level = -1; + } + aggressionLevel.setText( "Aggression level: " + convertAggressionLevel( level ) ); + aggression.setText( String.valueOf( level ) ); + } + }); + + final Lambda1 update = new Lambda1(){ + public Object invoke( Object c ){ + Character guy = (Character) c; + x.setText( String.valueOf( guy.getX() ) ); + y.setText( String.valueOf( guy.getY() ) ); + return null; + } + }; + + character.addListener( update ); + + block.setModel( new MinMaxSpinnerModel( findBlock( level ), 1, level.getBlocks().size() ) ); + final JSpinner map = (JSpinner) engine.find( "map" ); + map.setModel( new MinMaxSpinnerModel( character.getMap(), 0, character.getMaxMaps() ) ); + + final JButton save = (JButton) engine.find( "save" ); + final JButton close = (JButton) engine.find( "close" ); + + save.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + int xInt = Integer.parseInt( x.getText() ); + int yInt = Integer.parseInt( y.getText() ); + character.setName( name.getText() ); + character.setHealth( Integer.parseInt( health.getText() ) ); + character.setX( xInt ); + character.setY( yInt ); + character.setId(((Integer) id.getValue()).intValue()); + character.setMap( ((Integer) map.getValue()).intValue() ); + int a = Integer.parseInt( aggression.getText() ); + character.setAggression( a ); + Block b = getBlock( ((Integer) block.getValue()).intValue(), level ); + Block old = level.findBlock( character ); + if ( b != null && old != null && b != old ){ + old.removeThing( character ); + b.addThing( character ); + } + + character.removeListener( update ); + closeProc.invoke_(); + } + }); + + close.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + character.removeListener( update ); + closeProc.invoke_(); + } + }); + + return (JPanel) engine.getRootComponent(); + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/level/Editor.java b/editor/src/main/java/com/rafkind/paintown/level/Editor.java new file mode 100644 index 000000000..5012a8ae8 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/level/Editor.java @@ -0,0 +1,1644 @@ +package com.rafkind.paintown.level; + +// set softtabstop=3 +// set expandtab +// set shiftwidth=3 + +import java.util.*; +import java.awt.*; +import java.awt.image.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import java.io.*; + +import java.util.List; + +import com.rafkind.paintown.exception.LoadException; + +import com.rafkind.paintown.level.objects.Level; +import com.rafkind.paintown.level.objects.Block; +import com.rafkind.paintown.level.objects.Thing; +import com.rafkind.paintown.level.objects.Character; +import com.rafkind.paintown.level.objects.Item; +import com.rafkind.paintown.Closer; +import com.rafkind.paintown.Data; +import com.rafkind.paintown.Lambda0; +import com.rafkind.paintown.Lambda1; +import com.rafkind.paintown.Lambda2; +import com.rafkind.paintown.CloseHook; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.TokenReader; +import com.rafkind.paintown.RelativeFileChooser; +import javax.swing.filechooser.FileFilter; + +import org.swixml.SwingEngine; + +public class Editor extends JFrame { + + /* global thing for copy/pasting */ + private Thing copy; + + public Editor(){ + super("Paintown Editor"); + this.setSize( (int)(Toolkit.getDefaultToolkit().getScreenSize().getWidth() * 4.0/5.0), (int)(Toolkit.getDefaultToolkit().getScreenSize().getHeight() * 4.0/5.0)); + + Closer.open(); + + JMenuBar menuBar = new JMenuBar(); + JMenu menuProgram = new JMenu( "Program" ); + JMenuItem quit = new JMenuItem( "Quit" ); + JMenuItem data = new JMenuItem( "Data path" ); + JMenuItem animationEditor = new JMenuItem("Run character animation editor"); + menuProgram.add(animationEditor); + menuProgram.add( data ); + menuProgram.add( quit ); + menuBar.add( menuProgram ); + JMenu menuLevel = new JMenu( "Level" ); + menuBar.add( menuLevel ); + final Lambda0 closeHook = new Lambda0(){ + public Object invoke(){ + Closer.close(); + return null; + } + }; + JMenuItem newLevel = new JMenuItem( "New Level" ); + menuLevel.add( newLevel ); + JMenuItem loadLevel = new JMenuItem( "Open Level" ); + menuLevel.add( loadLevel ); + JMenuItem saveLevel = new JMenuItem( "Save Level" ); + menuLevel.add( saveLevel ); + JMenuItem saveLevelAs = new JMenuItem( "Save Level As" ); + menuLevel.add( saveLevelAs ); + JMenuItem closeLevel = new JMenuItem( "Close Level" ); + menuLevel.add( closeLevel ); + + menuProgram.setMnemonic( KeyEvent.VK_P ); + data.setMnemonic( KeyEvent.VK_D ); + quit.setMnemonic( KeyEvent.VK_Q ); + quit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, Event.CTRL_MASK)); + newLevel.setMnemonic( KeyEvent.VK_N ); + newLevel.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, Event.CTRL_MASK)); + menuLevel.setMnemonic( KeyEvent.VK_L ); + saveLevel.setMnemonic( KeyEvent.VK_S ); + + saveLevel.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, Event.CTRL_MASK)); + saveLevelAs.setMnemonic( KeyEvent.VK_A ); + saveLevelAs.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, Event.CTRL_MASK)); + loadLevel.setMnemonic( KeyEvent.VK_O ); + loadLevel.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, Event.CTRL_MASK)); + closeLevel.setMnemonic( KeyEvent.VK_W ); + closeLevel.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, Event.CTRL_MASK)); + + final JTabbedPane tabbed = new JTabbedPane(); + this.getContentPane().add( tabbed ); + + quit.addActionListener( new ActionListener(){ + public void actionPerformed( ActionEvent event ){ + closeHook.invoke_(); + } + }); + + animationEditor.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + String[] args = new String[0]; + com.rafkind.paintown.animator.Animator2.main(args); + } + }); + + data.addActionListener( new ActionListener(){ + public void actionPerformed( ActionEvent event ){ + /* just a container for an object */ + class ObjectBox { + private Object internal; + + public ObjectBox(){ + } + + public void set( Object o ){ + internal = o; + } + + public Object get(){ + return internal; + } + } + final SwingEngine engine = new SwingEngine( "data-path.xml" ); + final JTextField path = (JTextField) engine.find( "path" ); + final ObjectBox box = new ObjectBox(); + box.set(Data.getDataPath()); + path.setText(Data.getDataPath().getPath() ); + final JButton change = (JButton) engine.find( "change" ); + change.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + JFileChooser chooser = new JFileChooser( new File( "." ) ); + chooser.setFileSelectionMode( JFileChooser.DIRECTORIES_ONLY ); + int returnVal = chooser.showOpenDialog( Editor.this ); + if ( returnVal == JFileChooser.APPROVE_OPTION ){ + final File newPath = chooser.getSelectedFile(); + path.setText( newPath.getPath() ); + box.set( newPath ); + } + } + }); + final JButton save = (JButton) engine.find( "save" ); + final JButton cancel = (JButton) engine.find( "cancel" ); + final JDialog dialog = new JDialog( Editor.this, "Paintown data path" ); + save.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + Data.setDataPath( (File) box.get() ); + dialog.setVisible( false ); + } + }); + cancel.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + dialog.setVisible( false ); + } + }); + JPanel panel = (JPanel) engine.getRootComponent(); + dialog.getContentPane().add( panel ); + dialog.setSize( 300, 300 ); + dialog.setVisible( true ); + } + }); + + final HashMap levels = new HashMap(); + + final Lambda2 doSave = new Lambda2(){ + public Object invoke( Object level_, Object file_ ) throws IOException { + final Level level = (Level) level_; + final File file = (File) file_; + FileOutputStream out = new FileOutputStream( file ); + new PrintStream( out ).print( level.toToken().toString() + "\n" ); + out.close(); + System.out.println( level.toToken().toString() ); + return null; + } + }; + + newLevel.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + Level level = new Level(); + /* add 3 blocks to get the user started */ + level.getBlocks().add( new Block() ); + level.getBlocks().add( new Block() ); + level.getBlocks().add( new Block() ); + + levels.put( tabbed.add( createEditPanel( level ) ), level ); + } + }); + + saveLevel.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + if ( tabbed.getSelectedComponent() != null ){ + Level level = (Level) levels.get( tabbed.getSelectedComponent() ); + File file = level.getPath(); + if ( file == null ){ + file = userSelectFile("Save level"); + } + /* write the text to a file */ + if ( file != null ){ + try{ + doSave.invoke( level, file ); + level.setPath( file ); + tabbed.setTitleAt( tabbed.getSelectedIndex(), file.getName() ); + } catch ( Exception e ){ + e.printStackTrace(); + showError( "Could not save " + file + " because " + e.getMessage() ); + } + } + } + } + }); + + saveLevelAs.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + if ( tabbed.getSelectedComponent() != null ){ + Level level = (Level) levels.get( tabbed.getSelectedComponent() ); + File file = userSelectFile("Save level as"); + /* write the text to a file */ + if ( file != null ){ + try{ + doSave.invoke( level, file ); + level.setPath( file ); + tabbed.setTitleAt( tabbed.getSelectedIndex(), file.getName() ); + } catch ( Exception e ){ + e.printStackTrace(); + showError( "Could not save " + file + " because " + e.getMessage() ); + } + } + } + } + }); + + closeLevel.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + if ( tabbed.getSelectedComponent() != null ){ + levels.remove( tabbed.getSelectedComponent() ); + tabbed.remove( tabbed.getSelectedComponent() ); + } + } + }); + + loadLevel.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + JFileChooser chooser = new JFileChooser( new File( "." ) ); + chooser.setFileFilter( new FileFilter(){ + public boolean accept( File f ){ + return f.isDirectory() || f.getName().endsWith( ".txt" ); + } + + public String getDescription(){ + return "Level files"; + } + }); + + // chooser.setFileSelectionMode( JFileChooser.FILES_ONLY ); + int returnVal = chooser.showOpenDialog( Editor.this ); + if ( returnVal == JFileChooser.APPROVE_OPTION ){ + final File f = chooser.getSelectedFile(); + try{ + Level level = new Level( f ); + levels.put( tabbed.add( f.getName(), createEditPanel( level ) ), level ); + } catch ( LoadException le ){ + showError( "Could not load " + f.getName() ); + System.out.println( "Could not load " + f.getName() ); + le.printStackTrace(); + } + } + } + }); + + this.setJMenuBar( menuBar ); + this.addWindowListener( new CloseHook( closeHook ) ); + } + + public static File dataPath( File f ){ + return new File(Data.getDataPath().getPath() + "/" + f.getPath() ); + } + + public static String dataPath( String s ){ + return Data.getDataPath().getPath() + "/" + s; + } + + private String generateBoysName(){ + return new RandomNameAction( "boys.txt" ){ + public void actionPerformed( ActionEvent event ){ + } + }.generateName(); + } + + private String generateGirlsName(){ + return new RandomNameAction( "girls.txt" ){ + public void actionPerformed( ActionEvent event ){ + } + }.generateName(); + } + + private void smoothScroll( final JScrollBar scroll, final int start, final int end ){ + new Thread(){ + public void run(){ + int begin = start; + for ( int i = 0; i < 6; i++ ){ + int to = (begin + end) / 2; + scroll.setValue( to ); + begin = to; + try{ + Thread.sleep( 20 ); + } catch ( Exception e ){ + } + } + scroll.setValue( end ); + } + }.start(); + } + + private Thing getCopy(){ + return copy; + } + + private void setCopy( Thing t ){ + copy = t; + } + + private File userSelectFile(String title){ + JFileChooser chooser = new JFileChooser( new File( "." ) ); + chooser.setDialogTitle(title); + int returnVal = chooser.showOpenDialog( Editor.this ); + if ( returnVal == JFileChooser.APPROVE_OPTION ){ + return chooser.getSelectedFile(); + } else { + return null; + } + } + + /* provide default list of objects that can be added to the level */ + private List defaultObjects(){ + List data = new ArrayList(); + data.add( new File( "chars/angel/angel.txt" ) ); + data.add( new File( "chars/billy/billy.txt" ) ); + data.add( new File( "chars/eiji/eiji.txt" ) ); + data.add( new File( "chars/heavy/heavy.txt" ) ); + data.add( new File( "chars/jhun/jhun.txt" ) ); + data.add( new File( "chars/joe/joe.txt" ) ); + data.add( new File( "chars/punk/punk.txt" ) ); + data.add( new File( "chars/ralf/ralf.txt" ) ); + data.add( new File( "chars/robert/robert.txt" ) ); + data.add( new File( "chars/rugal/rugal.txt" ) ); + data.add( new File( "chars/shermie/shermie.txt" ) ); + data.add( new File( "chars/yamazaki/yamazaki.txt" ) ); + data.add( new File( "chars/yashiro/yashiro.txt" ) ); + data.add( new File( "misc/apple/apple.txt" ) ); + data.add( new File( "misc/cake/cake.txt" ) ); + data.add( new File( "misc/chicken/chicken.txt" ) ); + data.add( new File( "misc/cat/cat.txt" ) ); + return data; + } + + private JSplitPane createEditPanel( final Level level ){ + final SwingEngine engine = new SwingEngine( "main.xml" ); + + final JPanel viewContainer = (JPanel) engine.find( "view" ); + final JScrollPane viewScroll = new JScrollPane( JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS ); + final JPanel view = new JPanel(){ + + public Dimension getPreferredSize(){ + return level.getSize(); + } + + protected void paintComponent( Graphics g ){ + JScrollBar h = viewScroll.getHorizontalScrollBar(); + JScrollBar v = viewScroll.getVerticalScrollBar(); + g.setColor( new Color( 64, 64, 64 ) ); + g.fillRect( 0, 0, (int) level.getWidth(), v.getVisibleAmount() ); + g.clearRect( 0, (int) v.getVisibleAmount() + 1, (int) level.getWidth(), (int) level.getHeight() ); + level.render( (Graphics2D) g, h.getValue(), 0, h.getVisibleAmount(), v.getVisibleAmount() ); + } + }; + + viewScroll.setPreferredSize( new Dimension( 200, 200 ) ); + viewScroll.setViewportView( view ); + + /* this allows smooth scrolling of the level */ + viewScroll.getViewport().setScrollMode( JViewport.BACKINGSTORE_SCROLL_MODE ); + + /* + System.out.println( "JViewport.BLIT_SCROLL_MODE = " + JViewport.BLIT_SCROLL_MODE ); + System.out.println( "JViewport.BACKINGSTORE_SCROLL_MODE = " + JViewport.BACKINGSTORE_SCROLL_MODE ); + System.out.println( "JViewport.SIMPLE_SCROLL_MODE = " + JViewport.SIMPLE_SCROLL_MODE ); + System.out.println( "View scroll mode: " + viewScroll.getViewport().getScrollMode() ); + */ + viewScroll.getHorizontalScrollBar().setBackground( new Color( 128, 255, 0 ) ); + + final Lambda1 editSelected = new Lambda1(){ + public Object invoke( Object t ){ + Thing thing = (Thing) t; + final JDialog dialog = new JDialog( Editor.this, "Edit" ); + dialog.setSize( 350, 300 ); + PropertyEditor editor = thing.getEditor(); + dialog.getContentPane().add( editor.createPane( level, new Lambda0(){ + public Object invoke(){ + dialog.setVisible( false ); + viewScroll.repaint(); + return null; + } + })); + dialog.setVisible( true ); + return null; + } + }; + + class ObjectListModel implements ListModel{ + private List data; + private List listeners; + public ObjectListModel(){ + this.data = defaultObjects(); + this.listeners = new ArrayList(); + } + + public void add( File file ){ + data.add( file ); + ListDataEvent event = new ListDataEvent( this, ListDataEvent.INTERVAL_ADDED, data.size(), data.size() ); + for ( Iterator it = listeners.iterator(); it.hasNext(); ){ + ListDataListener l = (ListDataListener) it.next(); + l.intervalAdded( event ); + } + } + + public void remove( int index ){ + data.remove( index ); + ListDataEvent event = new ListDataEvent( this, ListDataEvent.INTERVAL_REMOVED, index, index ); + for ( Iterator it = listeners.iterator(); it.hasNext(); ){ + ListDataListener l = (ListDataListener) it.next(); + l.intervalAdded( event ); + } + + } + + public List getAll(){ + return data; + } + + public void addListDataListener( ListDataListener l ){ + listeners.add( l ); + } + + public Object getElementAt( int index ){ + return this.data.get( index ); + } + + public int getSize(){ + return this.data.size(); + } + + public void removeListDataListener( ListDataListener l ){ + this.listeners.remove( l ); + } + }; + + final ObjectListModel objectsModel = new ObjectListModel(); + + class Mouser extends MouseMotionAdapter implements MouseInputListener { + Thing selected = null; + double dx, dy; + double sx, sy; + JDialog currentPopup; + + public Thing getSelected(){ + return selected; + } + + public void setSelected( Thing t ){ + selected = t; + } + + public void mouseDragged( MouseEvent event ){ + + if ( selected != null ){ + // System.out.println( "sx,sy: " + sx + ", " + sy + " ex,ey: " + (event.getX() / 2) + ", " + (event.getY() / 2) + " dx, dy: " + dx + ", " + dy ); + level.moveThing( selected, (int)(sx + event.getX() / level.getScale() - dx), (int)(sy + event.getY() / level.getScale() - dy) ); + viewScroll.repaint(); + } + } + + private boolean leftClick( MouseEvent event ){ + return event.getButton() == MouseEvent.BUTTON1; + } + + private boolean rightClick( MouseEvent event ){ + return event.getButton() == MouseEvent.BUTTON3; + } + + private void selectThing( MouseEvent event ){ + Thing t = findThingAt( event ); + Block has = null; + for ( Iterator it = level.getBlocks().iterator(); it.hasNext(); ){ + Block b = (Block) it.next(); + b.setHighlight( false ); + if ( t != null && b.hasThing( t ) ){ + has = b; + } + } + + if ( has != null ){ + has.setHighlight( true ); + viewScroll.repaint(); + } + + if ( selected == null && t != null ){ + // selected = findThingAt( event ); + selected = t; + selected.setSelected( true ); + sx = selected.getX(); + sy = selected.getY() + level.getMinZ(); + // System.out.println( "Y: " + selected.getY() + " minZ: " + level.getMinZ() ); + dx = event.getX() / level.getScale(); + dy = event.getY() / level.getScale(); + // System.out.println( "Found: " + selected + " at " + event.getX() + " " + event.getY() ); + } + + if ( getSelected() != null && event.getClickCount() == 2 ){ + try{ + editSelected.invoke( getSelected() ); + } catch ( Exception e ){ + e.printStackTrace(); + } + } + } + + private List findFiles( File dir, final String ending ){ + File[] all = dir.listFiles( new java.io.FileFilter(){ + public boolean accept( File path ){ + return path.isDirectory() || path.getName().endsWith( ending ); + } + }); + List files = new ArrayList(); + for ( int i = 0; i < all.length; i++ ){ + if ( all[ i ].isDirectory() ){ + files.addAll( findFiles( all[ i ], ending ) ); + } else { + files.add( all[ i ] ); + } + } + return files; + } + + private Block findBlock( MouseEvent event ){ + int x = (int)(event.getX() / level.getScale()); + // System.out.println( event.getX() + " -> " + x ); + return level.findBlock( x ); + /* + int total = 0; + for ( Iterator it = level.getBlocks().iterator(); it.hasNext(); ){ + Block b = (Block) it.next(); + if ( b.isEnabled() ){ + if ( x >= total && x <= total + b.getLength() ){ + return b; + } + total += b.getLength(); + } + } + return null; + */ + } + + private Thing makeThing( Token head, int x, int y, String path ) throws LoadException { + if ( head.getName().equals( "character" ) ){ + Token temp = new Token(); + temp.addToken( new Token( "character" ) ); + temp.addToken( new String[]{ "name", "TempName" } ); + temp.addToken( new String[]{ "coords", String.valueOf( x ), String.valueOf( y ) } ); + temp.addToken( new String[]{ "health", "40" } ); + temp.addToken( new String[]{ "path", path } ); + return new Character( temp ); + } else if ( head.getName().equals( "item" ) ){ + Token temp = new Token(); + temp.addToken( new Token( "item" ) ); + temp.addToken( new String[]{ "coords", String.valueOf( x ), String.valueOf( y ) } ); + temp.addToken( new String[]{ "path", path } ); + // System.out.println( "Make item from " + temp.toString() ); + return new Item( temp ); + } else if ( head.getName().equals( "cat" ) ){ + Token temp = new Token(); + temp.addToken( new Token( "item" ) ); + temp.addToken( new String[]{ "coords", String.valueOf( x ), String.valueOf( y ) } ); + temp.addToken( new String[]{ "path", path } ); + return new Item( temp ); + } + throw new LoadException( "Unknown type: " + head.getName() ); + } + + private Vector collectCharFiles(){ + return new Vector( objectsModel.getAll() ); + } + + public void showAddObject( final Block block ) throws EditorException { + int x = -1; + for ( Iterator it = level.getBlocks().iterator(); it.hasNext(); ){ + Block b1 = (Block) it.next(); + if ( b1 == block ){ + break; + } + if ( b1.isEnabled() ){ + x += b1.getLength(); + } + } + showAddObjectPopup( new MouseEvent( Editor.this, -1, 0, 0, (int)((x + block.getLength() / 2) * level.getScale()), (int)((level.getMinZ() + level.getMaxZ()) * level.getScale() / 2), 1, false ) ); + } + + private void showAddObjectPopup( final MouseEvent event ){ + // JPanel panel = new JPanel(); + final Vector files = collectCharFiles(); + Box panel = Box.createVerticalBox(); + final JList all = new JList( files ); + panel.add( new JScrollPane( all ) ); + JButton add = new JButton( "Add" ); + JButton close = new JButton( "Close" ); + Box buttons = Box.createHorizontalBox(); + buttons.add( add ); + buttons.add( close ); + panel.add( buttons ); + if ( currentPopup != null ){ + currentPopup.setVisible( false ); + } + final JDialog dialog = new JDialog( Editor.this, "Add" ); + dialog.getContentPane().add( panel ); + dialog.setSize( 220, 250 ); + dialog.setLocation( event.getX() - viewScroll.getHorizontalScrollBar().getValue(), event.getY() ); + close.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + dialog.setVisible( false ); + } + }); + currentPopup = dialog; + dialog.setVisible( true ); + + final Lambda1 addThing = new Lambda1(){ + private int mid( int a, int b, int c ){ + return Math.max( Math.min( b, c ), a ); + } + + public Object invoke( Object f ){ + File file = (File) f; + try{ + Block b = findBlock( event ); + if ( b != null ){ + TokenReader reader = new TokenReader( dataPath( file ) ); + Token head = reader.nextToken(); + int x = (int)(event.getX() / level.getScale()); + int y = (int)(event.getY() / level.getScale()); + for ( Iterator it = level.getBlocks().iterator(); it.hasNext(); ){ + Block b1 = (Block) it.next(); + if ( b1 == b ){ + break; + } + if ( b1.isEnabled() ){ + x -= b1.getLength(); + } + } + b.addThing( makeThing( head, x, mid( 0, y - level.getMinZ(), level.getMaxZ() - level.getMinZ() ), file.getPath() ) ); + /* + Character c = new Character( reader.nextToken() ); + b.add( new Character( reader.nextToken() ) ); + */ + viewScroll.repaint(); + } else { + // JOptionPane.showMessageDialog( null, "The cursor is not within a block. Either move the cursor or add a block.", "Paintown Editor Error", JOptionPane.ERROR_MESSAGE ); + showError( "The cursor is not within a block. Either move the cursor or add a block." ); + } + } catch ( LoadException e ){ + System.out.println( "Could not load " + file ); + e.printStackTrace(); + } + + return null; + } + }; + + all.addMouseListener( new MouseAdapter() { + public void mouseClicked( MouseEvent clicked ){ + if ( clicked.getClickCount() == 2 ){ + int index = all.locationToIndex( clicked.getPoint() ); + File f = (File) files.get( index ); + addThing.invoke_( f ); + dialog.setVisible( false ); + } + } + }); + + add.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + int index = all.getSelectedIndex(); + if ( index != -1 ){ + File f = (File) files.get( index ); + addThing.invoke_( f ); + dialog.setVisible( false ); + } + } + }); + } + + public void mousePressed( MouseEvent event ){ + if ( leftClick( event ) ){ + if ( selected != null ){ + selected.setSelected( false ); + } + selected = null; + selectThing( event ); + } else if ( rightClick( event ) ){ + showAddObjectPopup( event ); + } + } + + public void mouseExited( MouseEvent event ){ + if ( selected != null ){ + // selected = null; + viewScroll.repaint(); + } + } + + private Thing findThingAt( MouseEvent event ){ + return level.findThing( (int)(event.getX() / level.getScale()), (int)(event.getY() / level.getScale()) ); + } + + public void mouseClicked( MouseEvent event ){ + } + + public void mouseEntered( MouseEvent event ){ + } + + public void mouseReleased( MouseEvent event ){ + if ( selected != null ){ + // selected = null; + viewScroll.repaint(); + } + } + } + + final Mouser mousey = new Mouser(); + + view.addMouseMotionListener( mousey ); + view.addMouseListener( mousey ); + view.addMouseListener( new MouseAdapter(){ + public void mousePressed( MouseEvent event ){ + /* force focus to move to the view */ + view.requestFocusInWindow(); + } + }); + + JTabbedPane tabbed = (JTabbedPane) engine.find( "tabbed" ); + final Box holder = Box.createVerticalBox(); + final Box blocks = Box.createVerticalBox(); + holder.add( new JScrollPane( blocks ) ); + + holder.add( new JSeparator() ); + + class ObjectList implements ListModel { + /* list listeners */ + private List listeners; + private List things; + private Lambda1 update; + private Block current; + public ObjectList(){ + listeners = new ArrayList(); + things = new ArrayList(); + update = new Lambda1(){ + public Object invoke( Object t ){ + Thing thing = (Thing) t; + int count = 0; + for ( Iterator it = things.iterator(); it.hasNext(); count += 1 ){ + Thing current = (Thing) it.next(); + if ( current == thing ){ + contentsChanged( new ListDataEvent( this, ListDataEvent.CONTENTS_CHANGED, count, count ) ); + } + } + return null; + } + }; + } + + public void setBlock( Block b ){ + current = b; + if ( b == null ){ + this.things = new ArrayList(); + } else { + this.things = b.getThings(); + for ( Iterator it = things.iterator(); it.hasNext(); ){ + Thing t = (Thing) it.next(); + t.addListener( this.update ); + } + } + + contentsChanged( new ListDataEvent( this, ListDataEvent.CONTENTS_CHANGED, 0, 999999 ) ); + } + + private void contentsChanged( ListDataEvent event ){ + for ( Iterator it = listeners.iterator(); it.hasNext(); ){ + ListDataListener l = (ListDataListener) it.next(); + l.contentsChanged( event ); + } + } + + /* + public void update( int index ){ + ListDataEvent event = new ListDataEvent( this, ListDataEvent.CONTENTS_CHANGED, index, index + 1 ); + for ( Iterator it = listeners.iterator(); it.hasNext(); ){ + ListDataListener l = (ListDataListener) it.next(); + l.contentsChanged( event ); + } + } + */ + + public Block getBlock(){ + return current; + } + + public void addListDataListener( ListDataListener l ){ + listeners.add( l ); + } + + public Object getElementAt( int index ){ + return this.things.get( index ); + } + + public int getSize(){ + return this.things.size(); + } + + public void removeListDataListener( ListDataListener l ){ + this.listeners.remove( l ); + } + } + + final ObjectList objectList = new ObjectList(); + // final JList currentObjects = new JList( objectList ); + final SwingEngine blockObjectsEngine = new SwingEngine( "block-objects.xml" ); + // holder.add( new JLabel( "Objects" ) ); + // holder.add( new JScrollPane( currentObjects ) ); + final JButton objectsAdd = (JButton) blockObjectsEngine.find( "add" ); + final JButton objectsDelete = (JButton) blockObjectsEngine.find( "delete" ); + final JButton objectsAddRandom = (JButton) blockObjectsEngine.find( "add-random" ); + final JButton objectsDeleteAll = (JButton) blockObjectsEngine.find( "delete-all" ); + final JList currentObjects = (JList) blockObjectsEngine.find( "current" ); + currentObjects.setModel( objectList ); + holder.add( (JPanel) blockObjectsEngine.getRootComponent() ); + + holder.add( Box.createVerticalGlue() ); + + objectsDelete.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + Block block = objectList.getBlock(); + Thing t = (Thing) currentObjects.getSelectedValue(); + if ( t != null && block != null ){ + mousey.setSelected( null ); + block.removeThing( t ); + objectList.setBlock( block ); + viewScroll.repaint(); + } + } + }); + + objectsDeleteAll.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + Block block = objectList.getBlock(); + if ( block != null ){ + mousey.setSelected( null ); + block.removeAllThings(); + objectList.setBlock( block ); + viewScroll.repaint(); + } + } + }); + + objectsAdd.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + try{ + if ( objectList.getBlock() == null ){ + throw new EditorException( "Select a block" ); + } + mousey.showAddObject( objectList.getBlock() ); + } catch ( EditorException e ){ + showError( e ); + } + } + }); + + objectsAddRandom.addActionListener( new AbstractAction(){ + private int thingsToAdd = 4; + private RandomNameAction makeName = new RandomNameAction( "boys.txt" ){ + public void actionPerformed( ActionEvent event ){ + } + }; + + private int randomX(){ + if ( Math.random() > 0.5 ){ + return -((int)(Math.random() * 640) + 320); + } else { + return (int)(Math.random() * 800) + 350; + } + } + + private int randomY(){ + return (int)(Math.random() * (level.getMaxZ() - level.getMinZ())); + } + + private int randomHealth(){ + return (int)(Math.random() * 60) + 20; + } + + private Character make() throws LoadException { + File choose = (File) objectsModel.getElementAt( (int)(Math.random() * (objectsModel.getSize())) ); + Token temp = new Token(); + temp.addToken( new Token( "character" ) ); + temp.addToken( new String[]{ "name", "TempName" } ); + temp.addToken( new String[]{ "coords", String.valueOf( randomX() ), String.valueOf( randomY() ) } ); + temp.addToken( new String[]{ "health", String.valueOf( randomHealth() ) } ); + temp.addToken( new String[]{ "path", choose.getPath() } ); + Character guy = new Character( temp ); + guy.setMap( (int)(Math.random() * guy.getMaxMaps())); + return guy; + } + + public void actionPerformed( ActionEvent event ){ + try{ + if ( objectList.getBlock() == null ){ + throw new EditorException( "Select a block" ); + } + for ( int i = 0; i < thingsToAdd; i++ ){ + try{ + objectList.getBlock().addThing( make() ); + } catch ( LoadException e ){ + System.out.println( "Ignoring exception" ); + e.printStackTrace(); + } + } + objectList.setBlock( objectList.getBlock() ); + viewScroll.repaint(); + } catch ( EditorException e ){ + showError( e ); + } + } + }); + + /* if an object is selected highlight it and scroll over to it */ + currentObjects.addListSelectionListener( new ListSelectionListener(){ + public void valueChanged( ListSelectionEvent e ){ + Thing t = (Thing) currentObjects.getSelectedValue(); + if ( mousey.getSelected() != null ){ + Thing old = mousey.getSelected(); + old.setSelected( false ); + level.findBlock( old ).setHighlight( false ); + } + t.setSelected( true ); + mousey.setSelected( t ); + + /* the current X position within the world */ + int currentX = 0; + Block b = level.findBlock( t ); + b.setHighlight( true ); + + /* calculate absolute X position of the selected thing */ + for ( Iterator it = level.getBlocks().iterator(); it.hasNext(); ){ + Block next = (Block) it.next(); + if ( next == b ){ + break; + } + if ( next.isEnabled() ){ + currentX += next.getLength(); + } + } + + currentX += t.getX(); + /* show the object in the center of the view */ + int move = (int)(currentX * level.getScale() - viewScroll.getHorizontalScrollBar().getVisibleAmount() / 2); + + /* scroll over to the selected thing */ + // viewScroll.getHorizontalScrollBar().setValue( move ); + smoothScroll( viewScroll.getHorizontalScrollBar(), viewScroll.getHorizontalScrollBar().getValue(), move ); + + viewScroll.repaint(); + } + }); + + currentObjects.getInputMap().put( KeyStroke.getKeyStroke( KeyEvent.VK_DELETE, 0 ), "delete" ); + currentObjects.getActionMap().put( "delete", new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + Thing t = (Thing) currentObjects.getSelectedValue(); + if ( t != null ){ + mousey.setSelected( null ); + Block b = level.findBlock( t ); + b.removeThing( t ); + objectList.setBlock( b ); + viewScroll.repaint(); + } + } + }); + + currentObjects.addMouseListener( new MouseAdapter() { + public void mouseClicked( MouseEvent clicked ){ + if ( clicked.getClickCount() == 2 ){ + Thing t = (Thing) currentObjects.getSelectedValue(); + editSelected.invoke_( t ); + } + } + }); + + /* so the user can click on the scrolly pane */ + // viewScroll.setFocusable( true ); + view.setFocusable( true ); + + view.getInputMap().put( KeyStroke.getKeyStroke( KeyEvent.VK_DELETE, 0 ), "delete" ); + + /* ctrl-c */ + view.getInputMap().put( KeyStroke.getKeyStroke( KeyEvent.VK_C, 2 ), "copy" ); + /* ctrl-v */ + view.getInputMap().put( KeyStroke.getKeyStroke( KeyEvent.VK_V, 2 ), "paste" ); + /* ctrl-b */ + view.getInputMap().put( KeyStroke.getKeyStroke( KeyEvent.VK_B, 2 ), "change-boy-name" ); + view.getInputMap().put( KeyStroke.getKeyStroke( KeyEvent.VK_G, 2 ), "change-girl-name" ); + + currentObjects.getInputMap().put( KeyStroke.getKeyStroke( KeyEvent.VK_B, 2 ), "change-boy-name" ); + currentObjects.getInputMap().put( KeyStroke.getKeyStroke( KeyEvent.VK_G, 2 ), "change-girl-name" ); + + view.getActionMap().put( "delete", new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + if ( mousey.getSelected() != null ){ + Block b = level.findBlock( mousey.getSelected() ); + b.removeThing( mousey.getSelected() ); + mousey.setSelected( null ); + objectList.setBlock( b ); + viewScroll.repaint(); + } + } + }); + + view.getActionMap().put( "copy", new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + // System.out.println( "Copy object" ); + if ( mousey.getSelected() != null ){ + setCopy( mousey.getSelected().copy() ); + } + } + }); + + AbstractAction changeBoy = new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + if ( mousey.getSelected() != null && mousey.getSelected() instanceof Character ){ + Character guy = (Character) mousey.getSelected(); + guy.setName( Editor.this.generateBoysName() ); + } + } + }; + + AbstractAction changeGirl = new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + if ( mousey.getSelected() != null && mousey.getSelected() instanceof Character ){ + Character guy = (Character) mousey.getSelected(); + guy.setName( Editor.this.generateGirlsName() ); + } + } + }; + + view.getActionMap().put( "change-boy-name", changeBoy ); + view.getActionMap().put( "change-girl-name", changeGirl ); + currentObjects.getActionMap().put( "change-boy-name", changeBoy ); + currentObjects.getActionMap().put( "change-girl-name", changeGirl ); + + view.getActionMap().put( "paste", new AbstractAction(){ + private int calculateLength( List blocks ){ + int total = 0; + for ( Iterator it = blocks.iterator(); it.hasNext(); ){ + Block b = (Block) it.next(); + total += b.getLength(); + } + return total; + } + + public void actionPerformed( ActionEvent event ){ + /* middle of the current screen */ + int x = (int)(viewScroll.getHorizontalScrollBar().getValue() / level.getScale() + viewScroll.getHorizontalScrollBar().getVisibleAmount() / level.getScale() / 2 ); + /* in between the min and max z lines */ + int y = (int)((level.getMaxZ() - level.getMinZ()) / 2); + Block b = level.findBlock( x ); + if ( b != null && getCopy() != null ){ + Thing copy = getCopy().copy(); + /* x has to be relative to the beginning of the block */ + copy.setX( x - calculateLength( level.getBlocks().subList( 0, level.getBlocks().indexOf( b ) ) ) ); + copy.setY( y ); + b.addThing( copy ); + objectList.setBlock( b ); + viewScroll.repaint(); + } else { + System.out.println( "No block found at " + x ); + } + } + }); + + tabbed.add( "Blocks", holder ); + + final SwingEngine objectEngine = new SwingEngine( "objects.xml" ); + tabbed.add( "Objects", (JComponent) objectEngine.getRootComponent() ); + + // final JList objects = new JList( allowableObjects ); + final JList objects = (JList) objectEngine.find( "objects" ); + /* objectsModel is declared way up top */ + objects.setModel( objectsModel ); + + { + final JButton add = (JButton) objectEngine.find( "add" ); + final JButton remove = (JButton) objectEngine.find( "delete" ); + + add.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + RelativeFileChooser chooser = new RelativeFileChooser(Editor.this, "Choose a file", Data.getDataPath()); + int ret = chooser.open(); + if ( ret == RelativeFileChooser.OK ){ + final String path = chooser.getPath(); + objectsModel.add( new File( path ) ); + } + } + }); + + remove.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + int index = objects.getSelectedIndex(); + if ( index != -1 ){ + objectsModel.remove( index ); + } + } + }); + } + + final SwingEngine levelEngine = new SwingEngine( "level.xml" ); + // debugSwixml( levelEngine ); + final JPanel levelPane = (JPanel) levelEngine.getRootComponent(); + tabbed.add( "Level", levelPane ); + + final JSpinner levelMinZ = (JSpinner) levelEngine.find( "min-z" ); + final JSpinner levelMaxZ = (JSpinner) levelEngine.find( "max-z" ); + final JComboBox atmosphere = (JComboBox) levelEngine.find( "atmosphere" ); + final JTextField levelBackground = (JTextField) levelEngine.find( "background" ); + final JTextField levelDescription = (JTextField) levelEngine.find("description"); + final JButton levelChangeBackground = (JButton) levelEngine.find( "change-background" ); + final Vector frontPanelsData = new Vector(); + final JList frontPanels = (JList) levelEngine.find( "front-panels" ); + frontPanels.setListData( frontPanelsData ); + final Vector backPanelsData = new Vector(); + final JList backPanels = (JList) levelEngine.find( "back-panels" ); + final JTextArea order = (JTextArea) levelEngine.find( "order" ); + final JComboBox pickOrder = (JComboBox) levelEngine.find( "pick-order" ); + final JSlider backgroundParallax = (JSlider) levelEngine.find( "background-parallax-slider" ); + final JLabel backgroundAmount = (JLabel) levelEngine.find( "background-parallax-amount" ); + final JSlider foregroundParallax = (JSlider) levelEngine.find( "foreground-parallax-slider" ); + final JLabel foregroundAmount = (JLabel) levelEngine.find( "foreground-parallax-amount" ); + + foregroundAmount.setText( String.valueOf( level.getForegroundParallax() ) ); + backgroundAmount.setText( String.valueOf( level.getBackgroundParallax() ) ); + backgroundParallax.setValue( (int) level.getBackgroundParallax() ); + foregroundParallax.setValue( (int) (level.getForegroundParallax() * 10) ); + + backgroundParallax.addChangeListener( new ChangeListener(){ + public void stateChanged( ChangeEvent e ){ + level.setBackgroundParallax( (double) backgroundParallax.getValue() ); + backgroundAmount.setText( String.valueOf( level.getBackgroundParallax() ) ); + } + }); + + foregroundParallax.addChangeListener( new ChangeListener(){ + public void stateChanged( ChangeEvent e ){ + level.setForegroundParallax( (double) foregroundParallax.getValue() / 10.0 ); + foregroundAmount.setText( String.valueOf( level.getForegroundParallax() ) ); + } + }); + + levelDescription.setText(level.getDescription()); + levelDescription.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent e){ + level.setDescription(levelDescription.getText()); + } + }); + + atmosphere.addItem( null ); + atmosphere.addItem( "rain" ); + atmosphere.addItem( "snow" ); + atmosphere.addItem( "night" ); + atmosphere.addItem( "fog" ); + + atmosphere.setSelectedItem( level.getAtmosphere() ); + + atmosphere.addActionListener( new ActionListener(){ + public void actionPerformed( ActionEvent e ){ + level.setAtmosphere( (String) atmosphere.getSelectedItem() ); + } + }); + + atmosphere.setEditable( false ); + + class BackPanelCombo implements ComboBoxModel { + private Object selected; + private Vector data; + private List listeners; + + public BackPanelCombo( Vector data ){ + this.data = data; + this.listeners = new ArrayList(); + } + + public Object getSelectedItem(){ + return selected; + } + + public void update(){ + selected = null; + ListDataEvent event = new ListDataEvent( this, ListDataEvent.CONTENTS_CHANGED, 0, 99999 ); + for ( Iterator it = listeners.iterator(); it.hasNext(); ){ + ListDataListener l = (ListDataListener) it.next(); + l.contentsChanged( event ); + } + } + + public void setSelectedItem( Object i ){ + selected = i; + } + + public void addListDataListener( ListDataListener l ){ + listeners.add( l ); + } + + public Object getElementAt( int index ){ + return this.data.get( index ); + } + + public int getSize(){ + return this.data.size(); + } + + public void removeListDataListener( ListDataListener l ){ + this.listeners.remove( l ); + } + }; + + final BackPanelCombo comboModel = new BackPanelCombo( backPanelsData ); + pickOrder.setModel( comboModel ); + + final Lambda0 setOrderText = new Lambda0(){ + public Object invoke(){ + StringBuffer orderText = new StringBuffer(); + for ( Iterator it = level.getBackPanelOrder().iterator(); it.hasNext(); ){ + Integer num = (Integer) it.next(); + String name = level.getBackPanelName( num.intValue() ); + orderText.append( name ).append( "\n" ); + } + order.setText( orderText.toString() ); + return null; + } + }; + + { + final JButton add = (JButton) levelEngine.find( "add-order" ); + final JButton remove = (JButton) levelEngine.find( "remove-order" ); + add.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + String path = (String) pickOrder.getSelectedItem(); + if ( path != null ){ + level.addBackPanelOrder( path ); + setOrderText.invoke_(); + viewScroll.repaint(); + } + } + }); + + remove.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + level.removeLastOrder(); + setOrderText.invoke_(); + viewScroll.repaint(); + } + }); + } + + { /* force scope */ + final JButton add = (JButton) levelEngine.find( "add-front-panel" ); + add.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + RelativeFileChooser chooser = new RelativeFileChooser(Editor.this, "Choose a file", Data.getDataPath()); + int ret = chooser.open(); + if ( ret == RelativeFileChooser.OK ){ + try{ + final String path = chooser.getPath(); + level.addFrontPanel( path ); + frontPanelsData.add( path ); + frontPanels.setListData( frontPanelsData ); + viewScroll.repaint(); + } catch ( LoadException le ){ + le.printStackTrace(); + } + } + } + }); + + final JButton remove = (JButton) levelEngine.find( "delete-front-panel" ); + remove.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + if ( frontPanels.getSelectedValue() != null ){ + String path = (String) frontPanels.getSelectedValue(); + level.removeFrontPanel( path ); + frontPanelsData.remove( path ); + frontPanels.setListData( frontPanelsData ); + viewScroll.repaint(); + } + } + }); + } + + { /* force scope */ + final JButton add = (JButton) levelEngine.find( "add-back-panel" ); + add.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + RelativeFileChooser chooser = new RelativeFileChooser(Editor.this, "Choose a file", Data.getDataPath()); + int ret = chooser.open(); + if ( ret == RelativeFileChooser.OK ){ + try{ + final String path = chooser.getPath(); + level.addBackPanel( path ); + backPanelsData.add( path ); + backPanels.setListData( backPanelsData ); + comboModel.update(); + viewScroll.repaint(); + } catch ( LoadException le ){ + le.printStackTrace(); + } + } + } + }); + + final JButton remove = (JButton) levelEngine.find( "delete-back-panel" ); + remove.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + if ( backPanels.getSelectedValue() != null ){ + String path = (String) backPanels.getSelectedValue(); + level.removeBackPanel( path ); + backPanelsData.remove( path ); + backPanels.setListData( backPanelsData ); + setOrderText.invoke_(); + comboModel.update(); + viewScroll.repaint(); + } + } + }); + } + + levelMinZ.setModel( new SpinnerNumberModel() ); + levelMinZ.addChangeListener( new ChangeListener(){ + public void stateChanged( ChangeEvent e ){ + JSpinner spinner = (JSpinner) e.getSource(); + Integer i = (Integer) spinner.getValue(); + level.setMinZ( i.intValue() ); + viewScroll.repaint(); + } + }); + + levelMaxZ.setModel( new SpinnerNumberModel() ); + levelMaxZ.addChangeListener( new ChangeListener(){ + public void stateChanged( ChangeEvent e ){ + JSpinner spinner = (JSpinner) e.getSource(); + Integer i = (Integer) spinner.getValue(); + level.setMaxZ( i.intValue() ); + viewScroll.repaint(); + } + }); + + levelBackground.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + level.loadBackground( levelBackground.getText() ); + viewScroll.repaint(); + } + }); + + levelChangeBackground.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + RelativeFileChooser chooser = new RelativeFileChooser(Editor.this, "Choose a file", Data.getDataPath()); + int ret = chooser.open(); + if ( ret == RelativeFileChooser.OK ){ + final String path = chooser.getPath(); + level.loadBackground( path ); + levelBackground.setText( path ); + viewScroll.repaint(); + } + } + }); + + /* initialize all the other crap for a level */ + final Lambda1 loadLevelProperties = new Lambda1(){ + public Object invoke( Object level_ ){ + Level level = (Level) level_; + levelMinZ.setValue( new Integer( level.getMinZ() ) ); + levelMaxZ.setValue( new Integer( level.getMaxZ() ) ); + levelBackground.setText( level.getBackgroundFile() ); + frontPanelsData.clear(); + frontPanelsData.addAll( level.getFrontPanelNames() ); + frontPanels.setListData( frontPanelsData ); + backPanelsData.clear(); + backPanelsData.addAll( level.getBackPanelNames() ); + backPanels.setListData( backPanelsData ); + + setOrderText.invoke_(); + comboModel.update(); + return null; + } + }; + + /* mess around with layout nonsense */ + GridBagLayout layout = new GridBagLayout(); + viewContainer.setLayout( layout ); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.fill = GridBagConstraints.BOTH; + + constraints.weightx = 1; + constraints.weighty = 1; + layout.setConstraints( viewScroll, constraints ); + view.setBorder( BorderFactory.createLineBorder( new Color( 255, 0, 0 ) ) ); + viewContainer.add( viewScroll ); + + final Lambda2 setupBlocks = new Lambda2(){ + private void editBlockProperties( final Block block, final Lambda0 done ){ + final JDialog dialog = new JDialog( Editor.this, "Edit" ); + dialog.setSize( 200, 200 ); + final SwingEngine engine = new SwingEngine( "block.xml" ); + dialog.getContentPane().add( (JPanel) engine.getRootComponent() ); + + final JTextField length = (JTextField) engine.find( "length" ); + final JTextField finish = (JTextField) engine.find( "finish" ); + final JCheckBox isFinish = (JCheckBox) engine.find( "is-finish" ); + final JSpinner id = (JSpinner) engine.find("id"); + final JCheckBox isContinuous = (JCheckBox) engine.find( "is-continuous" ); + final JButton save = (JButton) engine.find( "save" ); + final JButton close = (JButton) engine.find( "close" ); + + length.setText( String.valueOf( block.getLength() ) ); + isContinuous.setSelected( block.isContinuous() ); + id.setValue(block.getId()); + isFinish.setSelected( block.isFinish() ); + finish.setEnabled( block.isFinish() ); + finish.setText( String.valueOf( block.getFinish() ) ); + isFinish.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + finish.setEnabled( isFinish.isSelected() ); + } + }); + + save.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + block.setLength( Integer.parseInt( length.getText() ) ); + block.setId(((Integer) id.getValue()).intValue()); + if ( isFinish.isSelected() ){ + block.setFinish( Integer.parseInt( finish.getText() ) ); + } else { + block.setFinish( 0 ); + } + block.setContinuous( isContinuous.isSelected() ); + done.invoke_(); + dialog.setVisible( false ); + } + }); + + close.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + dialog.setVisible( false ); + } + }); + + dialog.setVisible( true ); + } + + private void scrollToBlock( Block block ){ + int length = 0; + for ( Iterator it = level.getBlocks().iterator(); it.hasNext(); ){ + Block b = (Block) it.next(); + if ( b == block ){ + break; + } + if ( b.isEnabled() ){ + length += b.getLength(); + } + } + smoothScroll( viewScroll.getHorizontalScrollBar(), viewScroll.getHorizontalScrollBar().getValue(), (int)(length * level.getScale() - 15) ); + // viewScroll.getHorizontalScrollBar().setValue( (int)(length * level.getScale() - 10) ); + } + + /* self_ should be the 'setupBlocks' lambda so that it can + * call itself recursively + */ + public Object invoke( Object l, Object self_ ){ + final Lambda2 self = (Lambda2) self_; + final Level level = (Level) l; + blocks.removeAll(); + int n = 1; + int total = 0; + for ( Iterator it = level.getBlocks().iterator(); it.hasNext(); ){ + final Block block = (Block) it.next(); + Box stuff = Box.createHorizontalBox(); + JCheckBox check = new JCheckBox( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + JCheckBox c = (JCheckBox) event.getSource(); + block.setEnabled( c.isSelected() ); + view.revalidate(); + viewScroll.repaint(); + } + }); + + check.setToolTipText("Check this box to make the block appear in the game"); + + check.setSelected( true ); + stuff.add( check ); + final JButton button = new JButton( "Block " + n + " : " + block.getLength() ); + button.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + if ( block.isEnabled() ){ + scrollToBlock( block ); + objectList.setBlock( block ); + } + } + }); + stuff.add( button ); + stuff.add( Box.createHorizontalStrut( 3 ) ); + + JButton edit = new JButton( "Edit" ); + final int xnum = n; + edit.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + editBlockProperties( block, new Lambda0(){ + public Object invoke(){ + button.setText( "Block " + xnum + " : " + block.getLength() ); + view.revalidate(); + viewScroll.repaint(); + return null; + } + }); + } + }); + stuff.add( edit ); + stuff.add( Box.createHorizontalStrut( 3 ) ); + + JButton erase = new JButton( "Delete" ); + erase.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + mousey.setSelected( null ); + objectList.setBlock( null ); + level.getBlocks().remove( block ); + self.invoke_( level, self ); + view.repaint(); + } + }); + stuff.add( erase ); + + stuff.add( Box.createHorizontalGlue() ); + blocks.add( stuff ); + + total += block.getLength(); + n += 1; + } + blocks.add( Box.createVerticalGlue() ); + Box addf = Box.createHorizontalBox(); + JButton add = new JButton( "Add block" ); + add.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + Block b = new Block(); + level.getBlocks().add( b ); + self.invoke_( level, self ); + view.revalidate(); + viewScroll.repaint(); + } + }); + addf.add( add ); + addf.add( Box.createHorizontalGlue() ); + blocks.add( addf ); + Box f = Box.createHorizontalBox(); + f.add( new JLabel( "Total length " + total ) ); + f.add( Box.createHorizontalGlue() ); + blocks.add( f ); + blocks.revalidate(); + blocks.repaint(); + return null; + } + }; + + setupBlocks.invoke_( level, setupBlocks ); + loadLevelProperties.invoke_( level ); + + final JSlider scroll = (JSlider) engine.find( "level-scale" ); + final JLabel scale = (JLabel) engine.find( "scale" ); + scroll.addChangeListener( new ChangeListener(){ + public void stateChanged( ChangeEvent e ){ + level.setScale( (double) scroll.getValue() * 2.0 / scroll.getMaximum() ); + scale.setText( "Scale: " + level.getScale() ); + view.revalidate(); + viewScroll.repaint(); + } + }); + + /* + JPanel scroll = (JPanel) engine.find( "scroll" ); + final JScrollBar scrolly = new JScrollBar( JScrollBar.HORIZONTAL, 20, 0, 1, 20 ); + final JLabel scale = (JLabel) engine.find( "scale" ); + scroll.add( scrolly ); + scrolly.addAdjustmentListener( new AdjustmentListener(){ + public void adjustmentValueChanged( AdjustmentEvent e ){ + level.setScale( (double) e.getValue() * 2.0 / scrolly.getMaximum() ); + scale.setText( "Scale: " + level.getScale() ); + view.revalidate(); + viewScroll.repaint(); + } + }); + */ + + return (JSplitPane) engine.getRootComponent(); + } + + private void debugSwixml( SwingEngine engine ){ + Map all = engine.getIdMap(); + System.out.println( "Debugging swixml" ); + for ( Iterator it = all.entrySet().iterator(); it.hasNext(); ){ + Map.Entry entry = (Map.Entry) it.next(); + System.out.println( "Id: " + entry.getKey() + " = " + entry.getValue() ); + } + } + + private static void showError( String message ){ + JOptionPane.showMessageDialog( null, message, "Paintown Editor Error", JOptionPane.ERROR_MESSAGE ); + } + + private static void showError( EditorException e ){ + showError( e.getMessage() ); + } + + public static void main( String[] args ){ + + final Editor editor = new Editor(); + SwingUtilities.invokeLater(new Runnable(){ + public void run(){ + editor.setVisible( true ); + } + }); + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/level/EditorException.java b/editor/src/main/java/com/rafkind/paintown/level/EditorException.java new file mode 100644 index 000000000..62fe0bdfc --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/level/EditorException.java @@ -0,0 +1,7 @@ +package com.rafkind.paintown.level; + +public class EditorException extends Exception { + public EditorException(String s){ + super(s); + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/level/ItemEditor.java b/editor/src/main/java/com/rafkind/paintown/level/ItemEditor.java new file mode 100644 index 000000000..732f3475f --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/level/ItemEditor.java @@ -0,0 +1,226 @@ +package com.rafkind.paintown.level; + +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; + +import java.util.Iterator; + +import com.rafkind.paintown.level.objects.Item; +import com.rafkind.paintown.level.objects.Level; +import com.rafkind.paintown.level.objects.Block; +import com.rafkind.paintown.level.objects.Stimulation; +import com.rafkind.paintown.Lambda0; +import com.rafkind.paintown.Lambda1; +import com.rafkind.paintown.MinMaxSpinnerModel; + +import org.swixml.SwingEngine; + +public class ItemEditor implements PropertyEditor { + + private Item item; + + public ItemEditor(Item i){ + item = i; + } + + private int findBlock( Level level ){ + int i = 1; + for ( Iterator it = level.getBlocks().iterator(); it.hasNext(); ){ + Block b = (Block) it.next(); + if ( b.hasThing( item ) ){ + return i; + } + i += 1; + } + return i; + } + + private Block getBlock( int num, Level level ){ + return (Block) level.getBlocks().get( num - 1 ); + } + + public JComponent createPane( final Level level, final Lambda0 closeProc ){ + final SwingEngine engine = new SwingEngine( "item.xml" ); + + final JButton save = (JButton) engine.find( "save" ); + final JButton close = (JButton) engine.find( "close" ); + + final JTextField name = (JTextField) engine.find( "name" ); + name.setText( item.getName() ); + final JTextField x = (JTextField) engine.find( "x" ); + x.setText( String.valueOf( item.getX() ) ); + final JTextField y = (JTextField) engine.find( "y" ); + y.setText( String.valueOf( item.getY() ) ); + final JSpinner block = (JSpinner) engine.find( "block" ); + final JSpinner id = (JSpinner) engine.find("id"); + final JTextField path = (JTextField) engine.find( "path" ); + path.setText(item.getPath()); + path.setEditable(false); + + id.setValue(item.getId()); + + block.setModel(new MinMaxSpinnerModel(findBlock(level), 1, level.getBlocks().size())); + + final JSpinner healthSpinner = (JSpinner) engine.find("health-spinner"); + final JRadioButton healthRadio = (JRadioButton) engine.find("health"); + final JRadioButton invincibilityRadio = (JRadioButton) engine.find("invincibility"); + final JSpinner invincibilitySpinner = (JSpinner) engine.find("invincibility-spinner"); + final JRadioButton speedRadio = (JRadioButton) engine.find("speed"); + final JSpinner speedSpinner = (JSpinner) engine.find("speed-spinner"); + final JSpinner speedDurationSpinner = (JSpinner) engine.find("speed-duration"); + + /* get the stimulation from the existing item or make a new one */ + final Stimulation.HealthStimulation healthStimulation = item.getStimulation() != null && item.getStimulation() instanceof Stimulation.HealthStimulation ? new Stimulation.HealthStimulation((Stimulation.HealthStimulation) item.getStimulation()) : new Stimulation.HealthStimulation(); + + if (item.getStimulation() instanceof Stimulation.HealthStimulation){ + healthRadio.setSelected(true); + healthSpinner.setEnabled(true); + invincibilitySpinner.setEnabled(false); + speedSpinner.setEnabled(false); + speedDurationSpinner.setEnabled(false); + } + + if (item.getStimulation() instanceof Stimulation.InvincibilityStimulation){ + invincibilityRadio.setSelected(true); + invincibilitySpinner.setEnabled(true); + healthSpinner.setEnabled(false); + speedSpinner.setEnabled(false); + speedDurationSpinner.setEnabled(false); + } + + if (item.getStimulation() instanceof Stimulation.SpeedStimulation){ + speedRadio.setSelected(true); + speedDurationSpinner.setEnabled(true); + speedSpinner.setEnabled(true); + healthSpinner.setEnabled(false); + invincibilitySpinner.setEnabled(false); + } + + healthRadio.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + healthSpinner.setEnabled(true); + invincibilitySpinner.setEnabled(false); + speedSpinner.setEnabled(false); + speedDurationSpinner.setEnabled(false); + } + }); + + healthSpinner.setValue(new Integer(healthStimulation.getHealth())); + healthSpinner.addChangeListener(new ChangeListener(){ + public void stateChanged(ChangeEvent e){ + JSpinner spinner = healthSpinner; + Integer i = (Integer) spinner.getValue(); + healthStimulation.setHealth(i.intValue()); + } + }); + + final Stimulation.InvincibilityStimulation invincibilityStimulation = item.getStimulation() != null && item.getStimulation() instanceof Stimulation.InvincibilityStimulation ? new Stimulation.InvincibilityStimulation((Stimulation.InvincibilityStimulation) item.getStimulation()) : new Stimulation.InvincibilityStimulation(); + + invincibilityRadio.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + invincibilitySpinner.setEnabled(true); + healthSpinner.setEnabled(false); + speedSpinner.setEnabled(false); + speedDurationSpinner.setEnabled(false); + } + }); + + invincibilitySpinner.setValue(new Integer(invincibilityStimulation.getDuration())); + invincibilitySpinner.addChangeListener(new ChangeListener(){ + public void stateChanged(ChangeEvent e){ + JSpinner spinner = invincibilitySpinner; + Integer i = (Integer) spinner.getValue(); + invincibilityStimulation.setDuration(i.intValue()); + } + }); + + final Stimulation.SpeedStimulation speedStimulation = item.getStimulation() != null && item.getStimulation() instanceof Stimulation.SpeedStimulation ? new Stimulation.SpeedStimulation((Stimulation.SpeedStimulation) item.getStimulation()) : new Stimulation.SpeedStimulation(); + + speedRadio.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + healthSpinner.setEnabled(false); + invincibilitySpinner.setEnabled(false); + speedSpinner.setEnabled(true); + speedDurationSpinner.setEnabled(true); + } + }); + + speedSpinner.setModel(new SpinnerNumberModel(speedStimulation.getBoost(), 0.0, 100.0, 0.1)); + speedSpinner.addChangeListener(new ChangeListener(){ + public void stateChanged(ChangeEvent e){ + JSpinner spinner = speedSpinner; + Number i = (Number) spinner.getValue(); + speedStimulation.setBoost(i.doubleValue()); + } + }); + + speedDurationSpinner.setValue(new Integer(speedStimulation.getTicks())); + speedDurationSpinner.addChangeListener(new ChangeListener(){ + public void stateChanged(ChangeEvent e){ + JSpinner spinner = speedDurationSpinner; + Integer i = (Integer) spinner.getValue(); + speedStimulation.setTicks(i.intValue()); + } + }); + + final JRadioButton noneRadio = (JRadioButton) engine.find( "none" ); + noneRadio.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + healthSpinner.setEnabled(false); + invincibilitySpinner.setEnabled(false); + speedSpinner.setEnabled(false); + } + }); + + final Lambda1 update = new Lambda1(){ + public Object invoke(Object c){ + Item item = (Item) c; + x.setText(String.valueOf(item.getX())); + y.setText(String.valueOf(item.getY())); + return null; + } + }; + + item.addListener(update); + + save.addActionListener( new AbstractAction(){ + public void actionPerformed( ActionEvent event ){ + int xInt = Integer.parseInt( x.getText() ); + int yInt = Integer.parseInt( y.getText() ); + item.setName( name.getText() ); + item.setX( xInt ); + item.setY( yInt ); + item.setId(((Integer) id.getValue()).intValue()); + Block b = getBlock( ((Integer) block.getValue()).intValue(), level ); + Block old = level.findBlock( item ); + if (b != null && old != null && b != old){ + old.removeThing(item); + b.addThing(item); + } + + item.removeListener( update ); + closeProc.invoke_(); + + if (noneRadio.isSelected()){ + item.setStimulation(null); + } else if (healthRadio.isSelected()){ + item.setStimulation(healthStimulation); + } else if (invincibilityRadio.isSelected()){ + item.setStimulation(invincibilityStimulation); + } else if (speedRadio.isSelected()){ + item.setStimulation(speedStimulation); + } + } + }); + + close.addActionListener(new AbstractAction(){ + public void actionPerformed(ActionEvent event){ + item.removeListener(update); + closeProc.invoke_(); + } + }); + + return (JPanel) engine.getRootComponent(); + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/level/PropertyEditor.java b/editor/src/main/java/com/rafkind/paintown/level/PropertyEditor.java new file mode 100644 index 000000000..4950bd5fa --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/level/PropertyEditor.java @@ -0,0 +1,10 @@ +package com.rafkind.paintown.level; + +import javax.swing.JComponent; + +import com.rafkind.paintown.level.objects.Level; +import com.rafkind.paintown.Lambda0; + +public interface PropertyEditor{ + public JComponent createPane( Level level, Lambda0 closeProc ); +} diff --git a/editor/src/main/java/com/rafkind/paintown/level/RandomNameAction.java b/editor/src/main/java/com/rafkind/paintown/level/RandomNameAction.java new file mode 100644 index 000000000..89e21addb --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/level/RandomNameAction.java @@ -0,0 +1,42 @@ +package com.rafkind.paintown.level; + +import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; +import java.io.IOException; +import java.io.BufferedReader; +import java.io.InputStreamReader; + +import javax.swing.AbstractAction; + +public abstract class RandomNameAction extends AbstractAction { + private static HashMap files = new HashMap(); + private List names; + + private List readFile( BufferedReader reader ) throws IOException { + List l = new ArrayList(); + while ( reader.ready() ){ + l.add( reader.readLine() ); + } + return l; + } + + public RandomNameAction( String file ){ + if ( files.get( file ) == null ){ + try{ + files.put( file, readFile( new BufferedReader( new InputStreamReader( this.getClass().getResourceAsStream( "/" + file ) ) ) ) ); + names = (List) files.get( file ); + } catch ( IOException ie ){ + ie.printStackTrace(); + names = new ArrayList(); + names.add( "TempName" ); + } + } else { + names = (List) files.get( file ); + } + } + + public String generateName(){ + return (String) names.get( (int)(Math.random() * (names.size() - 1)) ); + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/level/objects/Block.java b/editor/src/main/java/com/rafkind/paintown/level/objects/Block.java new file mode 100644 index 000000000..a9b2a7210 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/level/objects/Block.java @@ -0,0 +1,218 @@ +package com.rafkind.paintown.level.objects; + +import com.rafkind.paintown.exception.LoadException; +import com.rafkind.paintown.Token; + +import java.awt.*; +import java.util.Iterator; +import java.util.List; +import java.util.ArrayList; +import java.util.TreeSet; +import java.util.Arrays; +import java.util.Comparator; + +public class Block{ + + private int length; + private int id; + private List objects; + private boolean enabled = true; + private boolean highlight = false; + private boolean continuous = false; + private int finish = 0; + + public Block( Token token ) throws LoadException { + Token l = token.findToken( "length" ); + if ( l != null ){ + length = l.readInt( 0 ); + } + + Token tid = token.findToken("id"); + if (tid != null){ + id = tid.readInt(0); + Level.checkBlockId(id); + } else { + id = Level.nextBlockId(); + } + + Token f = token.findToken( "finish" ); + if ( f != null ){ + finish = f.readInt( 0 ); + } + + Token b = token.findToken( "continuous" ); + if ( b != null ){ + continuous = true; + } + + objects = new ArrayList(); + for ( Iterator it = token.findTokens( "object" ).iterator(); it.hasNext(); ){ + Token t = (Token) it.next(); + Token type = t.findToken( "type" ); + if ( type != null ){ + String str = type.readString( 0 ); + if ( str.equals( "enemy" ) ){ + objects.add( new Character( t ) ); + } else if ( str.equals( "item" ) ){ + objects.add( new Item( t ) ); + } else if ( str.equals( "cat" ) ){ + objects.add( new Cat( t ) ); + } else { + System.out.println( "Warning: ignoring object of type '" + str + "' at line " + type.getLine() ); + } + } else { + throw new LoadException( "Object does not have a 'type' expression at line " + t.getLine() ); + } + } + } + + public Block(){ + this.objects = new ArrayList(); + this.length = 320; + } + + public void setFinish( int s ){ + finish = s; + } + + public boolean isFinish(){ + return finish > 0; + } + + public boolean isContinuous(){ + return continuous; + } + + public void setContinuous( boolean b ){ + this.continuous = b; + } + + public int getFinish(){ + return finish; + } + + public boolean isEnabled(){ + return enabled; + } + + public void setEnabled( boolean b ){ + enabled = b; + } + + public void addThing( Thing t ){ + objects.add( t ); + } + + public void removeThing( Thing t ){ + objects.remove( t ); + } + + public void removeAllThings(){ + objects.clear(); + } + + public void render( Graphics2D g, int x, int height, int minZ, int maxZ, int num ){ + Object[] objs = this.objects.toArray(); + Arrays.sort( objs, new Comparator(){ + public int compare( Object o1, Object o2 ){ + Thing t1 = (Thing) o1; + Thing t2 = (Thing) o2; + if ( t1.getY() < t2.getY() ){ + return -1; + } + if ( t1.getY() > t2.getY() ){ + return 1; + } + return 0; + } + + public boolean equals( Object o ){ + return false; + } + }); + g.translate( x, minZ ); + + for ( int i = 0; i < objs.length; i++ ){ + Thing t = (Thing) objs[ i ]; + g.setColor( new Color( 255, 255, 255 ) ); + t.render( g, getHighlight() ); + g.drawString( "Block " + num, t.getX(), t.getY() ); + } + g.translate( 0, -minZ ); + g.setColor( new Color( 255, 255, 255 ) ); + g.fillRect( 0, 0, 1, height ); + g.fillRect( getLength(), 0, 1, height ); + g.translate( -x, 0 ); + } + + public Thing findThing( int x, int y ){ + for ( Iterator it = this.objects.iterator(); it.hasNext(); ){ + Thing t = (Thing) it.next(); + // System.out.println( "Check " + t + " X1: " + t.getX1() + " Y1: " + t.getY1() + " X2: " + t.getX2() + " Y2: " + t.getY2() + " vs " + x + ", " + y ); + if ( x >= t.getX1() && x <= t.getX2() && + y >= t.getY1() && y <= t.getY2() ){ + return t; + } + } + return null; + } + + public boolean hasThing( Thing t ){ + // System.out.println( this + " contains = " + this.objects.contains( t ) ); + for ( Iterator it = objects.iterator(); it.hasNext(); ){ + if ( it.next() == t ){ + return true; + } + } + return false; + // return this.objects.contains( t ); + } + + public Token toToken(){ + Token block = new Token(); + block.addToken( new Token( "block" ) ); + // block.addToken( new Token().addToken( new Token( "length" ) ).addToken( new Token( String.valueOf( getLength() ) ) ) ); + block.addToken(new String[]{"id", String.valueOf(getId())}); + block.addToken( new String[]{ "length", String.valueOf( getLength() ) } ); + if ( isContinuous() ){ + block.addToken( new String[]{ "continuous" } ); + } + + if ( isFinish() ){ + block.addToken( new String[]{ "finish", String.valueOf( getFinish() ) } ); + } + for ( Iterator it = objects.iterator(); it.hasNext(); ){ + Thing t = (Thing) it.next(); + block.addToken( t.toToken() ); + } + return block; + } + + public List getThings(){ + return this.objects; + } + + public void setHighlight( boolean h ){ + this.highlight = h; + } + + public boolean getHighlight(){ + return this.highlight; + } + + public void setLength( int i ){ + length = i; + } + + public int getLength(){ + return length; + } + + public int getId(){ + return id; + } + + public void setId(int id){ + this.id = id; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/level/objects/Cat.java b/editor/src/main/java/com/rafkind/paintown/level/objects/Cat.java new file mode 100644 index 000000000..292b4867e --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/level/objects/Cat.java @@ -0,0 +1,95 @@ +package com.rafkind.paintown.level.objects; + +import java.awt.*; +import java.awt.image.*; + +import java.io.File; +import java.io.IOException; + +import java.util.List; +import java.util.Iterator; + +import com.rafkind.paintown.exception.LoadException; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.level.PropertyEditor; +import com.rafkind.paintown.level.CatEditor; +import com.rafkind.paintown.TokenReader; +import com.rafkind.paintown.MaskedImage; +import com.rafkind.paintown.level.Editor; + +public class Cat extends Thing { + + public Cat( Token token ) throws LoadException { + super( token ); + Token name = token.findToken( "name" ); + if ( name != null ){ + setName( name.readString( 0 ) ); + } + } + + public Cat( Cat copy ){ + super( copy ); + } + + private Token findIdle( List tokens ){ + for ( Iterator it = tokens.iterator(); it.hasNext(); ){ + Token t = (Token) it.next(); + Token name = t.findToken( "name" ); + if ( name != null ){ + if ( name.readString( 0 ).equals( "idle1" ) ){ + return t; + } + } + } + return null; + } + + protected BufferedImage readIdleImage( String file ) throws LoadException { + TokenReader reader = new TokenReader( new File( file ) ); + Token head = reader.nextToken(); + Token idle = null; + idle = findIdle( head.findTokens( "animation" ) ); + + if ( idle != null ){ + String base = "./"; + Token basedir = idle.findToken( "basedir" ); + if ( basedir != null ){ + base = basedir.readString( 0 ); + } + Token frame = idle.findToken( "frame" ); + if ( frame != null ){ + String pic = frame.readString( 0 ); + try{ + return MaskedImage.load( Editor.dataPath( base + pic ) ); + } catch ( IOException ie ){ + throw new LoadException( "Could not load " + base + pic + " at line " + frame.getLine(), ie ); + } + } + } + throw new LoadException( "No idle animation given for " + file ); + } + + public Thing copy(){ + return new Cat( this ); + } + + protected String getType(){ + return "cat"; + } + + public PropertyEditor getEditor(){ + return new CatEditor( this ); + } + + public Token toToken(){ + Token thing = new Token(); + thing.addToken( new Token( "object" ) ); + thing.addToken( new String[]{"id", String.valueOf(getId())} ); + thing.addToken( new String[]{"name", "\"" + getName() + "\"" } ); + thing.addToken( new String[]{"type", getType() } ); + thing.addToken( new String[]{"path", getPath() } ); + thing.addToken( new String[]{"coords", String.valueOf( getX() ), String.valueOf( getY() ) } ); + + return thing; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/level/objects/Character.java b/editor/src/main/java/com/rafkind/paintown/level/objects/Character.java new file mode 100644 index 000000000..094ff7d23 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/level/objects/Character.java @@ -0,0 +1,271 @@ +package com.rafkind.paintown.level.objects; + +import java.awt.*; +import java.io.*; +import java.awt.image.*; + +import java.util.List; +import java.util.Iterator; +import java.util.HashMap; + +import com.rafkind.paintown.exception.LoadException; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.TokenReader; +import com.rafkind.paintown.MaskedImage; +import com.rafkind.paintown.level.PropertyEditor; +import com.rafkind.paintown.level.CharacterEditor; +import com.rafkind.paintown.Lambda0; +import com.rafkind.paintown.level.Editor; + +public class Character extends Thing { + + private HashMap remaps; + private int health; + private int map; + private int maxMaps; + private int aggression; + + public Character( Token token ) throws LoadException { + super( token ); + + Token alias = token.findToken( "name" ); + if ( alias == null ){ + alias = token.findToken( "alias" ); + } + if ( alias != null ){ + setName( alias.readString( 0 ) ); + } + Token remap = token.findToken( "map" ); + if ( remap != null ){ + setMap( remap.readInt( 0 ) ); + } else { + setMap( 0 ); + } + Token aggr = token.findToken( "aggression" ); + if ( aggr != null ){ + setAggression( aggr.readInt( 0 ) ); + } else { + setAggression( -1 ); + } + + Token health = token.findToken( "health" ); + if ( health != null ){ + setHealth( health.readInt( 0 ) ); + } else { + throw new LoadException( "No health token given" ); + } + + Token data = new TokenReader( new File( Editor.dataPath( getPath() ) ) ).nextToken(); + maxMaps = calculateMaxMaps( data ); + + remaps = new HashMap(); + + this.remaps.put( new Integer( 0 ), new Lambda0(){ + public Object invoke(){ + return getMain(); + } + }); + + int num = 1; + for ( Iterator it = data.findTokens( "remap" ).iterator(); it.hasNext(); ){ + Token re = (Token) it.next(); + final String normalPath = re.readString( 0 ); + final String altPath = re.readString( 1 ); + final int tnum = num; + synchronized( remaps ){ + this.remaps.put( new Integer( tnum ), new Lambda0(){ + public Object invoke(){ + try{ + final Image alt = createRemap( Editor.dataPath( normalPath ), Editor.dataPath( altPath ), getMain() ); + synchronized( remaps ){ + remaps.put( new Integer( tnum ), new Lambda0(){ + public Object invoke(){ + return alt; + } + }); + } + return alt; + } catch ( IOException ie ){ + ie.printStackTrace(); + synchronized( remaps ){ + remaps.put( new Integer( tnum ), new Lambda0(){ + public Object invoke(){ + return getMain(); + } + }); + } + return getMain(); + } + } + }); + } + num += 1; + } + } + + public Character( Character copy ){ + super( copy ); + setRemaps( copy.getRemaps() ); + setMap( copy.getMap() ); + setAggression( copy.getAggression() ); + setMaxMaps( copy.getMaxMaps() ); + setHealth( copy.getHealth() ); + } + + private HashMap getRemaps(){ + return remaps; + } + + private void setRemaps( HashMap remaps ){ + this.remaps = remaps; + } + + private Image createRemap( String normal, String alt, BufferedImage original ) throws IOException { + HashMap colors = new HashMap(); + BufferedImage old = MaskedImage.load( normal ); + BufferedImage xnew = MaskedImage.load( alt ); + BufferedImage map = new MaskedImage( original.getWidth( null ), original.getHeight( null ) ); + + for ( int x = 0; x < old.getWidth( null ); x += 1 ){ + for ( int y = 0; y < old.getHeight( null ); y += 1 ){ + int oldPixel = old.getRGB( x, y ); + int newPixel = xnew.getRGB( x, y ); + if ( oldPixel != newPixel ){ + colors.put( new Integer( oldPixel ), new Integer( newPixel ) ); + } + } + } + + for ( int x = 0; x < original.getWidth( null ); x += 1 ){ + for ( int y = 0; y < original.getHeight( null ); y += 1 ){ + int pixel = original.getRGB( x, y ); + Integer r = (Integer) colors.get( new Integer( pixel ) ); + if ( r != null ){ + pixel = r.intValue(); + } + map.setRGB( x, y, pixel ); + } + } + + return map; + } + + public Thing copy(){ + return new Character( this ); + } + + public void render( Graphics2D g, boolean highlight ){ + Lambda0 proc = null; + synchronized( remaps ){ + proc = (Lambda0) this.remaps.get( new Integer( getMap() ) ); + if ( proc == null ){ + proc = (Lambda0) this.remaps.get( new Integer( 0 ) ); + } + } + Image i = (Image) proc.invoke_(); + render( i, g, highlight ); + } + + public int getAggression(){ + return aggression; + } + + public void setAggression( int a ){ + this.aggression = a; + } + + public int getMap(){ + return this.map; + } + + public void setMap( int m ){ + this.map = m; + } + + public int getMaxMaps(){ + return maxMaps; + } + + private void setMaxMaps( int i ){ + maxMaps = i; + } + + private int calculateMaxMaps( Token head ) throws LoadException { + return head.findTokens( "remap" ).size(); + } + + protected BufferedImage readIdleImage( String file ) throws LoadException { + TokenReader reader = new TokenReader( new File( file ) ); + Token head = reader.nextToken(); + Token idle = null; + idle = findIdle( head.findTokens( "anim" ) ); + + if ( idle != null ){ + String base = "./"; + Token basedir = idle.findToken( "basedir" ); + if ( basedir != null ){ + base = basedir.readString( 0 ); + } + Token frame = idle.findToken( "frame" ); + if ( frame != null ){ + String pic = frame.readString( 0 ); + try{ + return MaskedImage.load( Editor.dataPath( base + pic ) ); + } catch ( IOException ie ){ + throw new LoadException( "Could not load " + base + pic + " at line " + frame.getLine(), ie ); + } + } + } + throw new LoadException( "No idle animation given for " + file ); + } + + public int getHealth(){ + return health; + } + + public void setHealth( int i ){ + health = i; + } + + private Token findIdle( List tokens ){ + for ( Iterator it = tokens.iterator(); it.hasNext(); ){ + Token t = (Token) it.next(); + Token name = t.findToken( "name" ); + if ( name != null ){ + if ( name.readString( 0 ).equals( "idle" ) ){ + return t; + } + } + } + return null; + } + + public String toString(){ + return super.toString() + " Health: " + getHealth(); + } + + protected String getType(){ + return "enemy"; + } + + public PropertyEditor getEditor(){ + return new CharacterEditor( this ); + } + + public Token toToken(){ + Token thing = new Token(); + thing.addToken( new Token( "object" ) ); + thing.addToken( new String[]{ "type", getType() } ); + thing.addToken( new String[]{"id", String.valueOf(getId())} ); + thing.addToken( new String[]{ "name", "\"" + getName() + "\"" } ); + thing.addToken( new String[]{ "path", "\"" + getPath() + "\"" } ); + thing.addToken( new String[]{ "map", String.valueOf( getMap() ) } ); + thing.addToken( new String[]{ "health", String.valueOf( getHealth() ) } ); + if ( getAggression() != -1 ){ + thing.addToken( new String[]{ "aggression", String.valueOf( getAggression() ) } ); + } + thing.addToken( new String[]{ "coords", String.valueOf( getX() ), String.valueOf( getY() ) } ); + return thing; + } + +} diff --git a/editor/src/main/java/com/rafkind/paintown/level/objects/Item.java b/editor/src/main/java/com/rafkind/paintown/level/objects/Item.java new file mode 100644 index 000000000..1cc7dcfdf --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/level/objects/Item.java @@ -0,0 +1,97 @@ +package com.rafkind.paintown.level.objects; + +import java.awt.*; +import java.awt.image.*; + +import java.io.File; +import java.io.IOException; + +import com.rafkind.paintown.exception.LoadException; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.level.PropertyEditor; +import com.rafkind.paintown.level.ItemEditor; +import com.rafkind.paintown.TokenReader; +import com.rafkind.paintown.MaskedImage; +import com.rafkind.paintown.level.Editor; + +public class Item extends Thing { + + private Stimulation stimulation; + + public Item( Token token ) throws LoadException { + super( token ); + Token name = token.findToken( "name" ); + if ( name != null ){ + setName( name.readString( 0 ) ); + } + Token stim = token.findToken( "stimulation" ); + if ( stim != null ){ + stimulation = Stimulation.load( stim ); + } + } + + public Item( Item copy ){ + super( copy ); + if ( copy.stimulation != null ){ + stimulation = copy.stimulation.copy(); + } + } + + public Stimulation getStimulation(){ + return stimulation; + } + + public void setStimulation( Stimulation s ){ + stimulation = s; + } + + protected BufferedImage readIdleImage( String file ) throws LoadException { + TokenReader reader = new TokenReader( new File( file ) ); + Token head = reader.nextToken(); + Token idle = head; + if ( idle != null ){ + String base = "./"; + Token basedir = idle.findToken( "basedir" ); + if ( basedir != null ){ + base = basedir.readString( 0 ); + } + Token frame = idle.findToken( "frame" ); + if ( frame != null ){ + String pic = frame.readString( 0 ); + try{ + return MaskedImage.load( Editor.dataPath( base + pic ) ); + } catch ( IOException ie ){ + throw new LoadException( "Could not load " + base + pic + " at line " + frame.getLine(), ie ); + } + } + } + throw new LoadException( "No idle animation given for " + file ); + } + + public Thing copy(){ + return new Item( this ); + } + + protected String getType(){ + return "item"; + } + + public PropertyEditor getEditor(){ + return new ItemEditor( this ); + } + + public Token toToken(){ + Token thing = new Token(); + thing.addToken( new Token( "object" ) ); + thing.addToken( new String[]{"id", String.valueOf(getId())} ); + thing.addToken( new String[]{ "name", "\"" + getName() + "\"" } ); + thing.addToken( new String[]{ "type", getType() } ); + thing.addToken( new String[]{ "path", getPath() } ); + thing.addToken( new String[]{ "coords", String.valueOf( getX() ), String.valueOf( getY() ) } ); + if ( stimulation != null ){ + thing.addToken( stimulation.toToken() ); + } + + return thing; + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/level/objects/Level.java b/editor/src/main/java/com/rafkind/paintown/level/objects/Level.java new file mode 100644 index 000000000..fa79403d8 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/level/objects/Level.java @@ -0,0 +1,639 @@ +package com.rafkind.paintown.level.objects; + +import java.awt.*; +import java.awt.image.*; +import java.io.*; +import javax.imageio.*; + +import java.util.Iterator; +import java.util.List; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import com.rafkind.paintown.exception.LoadException; +import com.rafkind.paintown.TokenReader; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.MaskedImage; +import com.rafkind.paintown.level.Editor; + +public class Level{ + + private String name; + private String intro = ""; + private String ending = ""; + private Image background; + private String backgroundFile; + private int width; + private List frontPanels; + private HashMap backPanels; + private List panelOrder; + private int minZ; + private int maxZ; + private double backgroundParallax; + private double foregroundParallax; + private double scale; + private File path; + private String atmosphere; + private String description; + private Token trigger; + + private List blocks; + + private static int nextId = 0; + private static int nextBlockId = 0; + + public static int nextId(){ + nextId += 1; + return nextId; + } + + public static void checkId(int i){ + if (i > nextId){ + nextId = i; + } + } + + public static void resetId(){ + nextId = 0; + } + + public static int nextBlockId(){ + nextBlockId += 1; + return nextBlockId; + } + + public static void checkBlockId(int i){ + if (i > nextBlockId){ + nextBlockId = i; + } + } + + public static void resetBlockId(){ + nextBlockId = 0; + } + + private static class Panel{ + public String name; + public Image image; + public Panel( String name, Image i ){ + this.name = name; + this.image = i; + } + } + + public Level(){ + initAll(); + this.path = null; + } + + public File getPath(){ + return path; + } + + public void setPath( File path ){ + this.path = path; + } + + public Level( File path ) throws LoadException { + this.path = path; + load( path ); + } + + public void setAtmosphere( String s ){ + this.atmosphere = s; + } + + public String getAtmosphere(){ + return this.atmosphere; + } + + public void setBackgroundParallax( double d ){ + this.backgroundParallax = d; + } + + public double getBackgroundParallax(){ + return this.backgroundParallax; + } + + public void setForegroundParallax( double d ){ + this.foregroundParallax = d; + } + + public double getForegroundParallax(){ + return this.foregroundParallax; + } + + public String getDescription(){ + return this.description; + } + + public void setDescription(String description){ + this.description = description; + } + + public List getBlocks(){ + return blocks; + } + + public int getMinZ(){ + return minZ; + } + + public int getMaxZ(){ + return maxZ; + } + + public void setMinZ( int z ){ + minZ = z; + } + + public void setMaxZ( int z ){ + maxZ = z; + } + + public void setIntro(String intro){ + this.intro = intro; + } + + public String getIntro(){ + return intro; + } + + public void setEnding(String ending){ + this.ending = ending; + } + + public String getEnding(){ + return ending; + } + + public void addFrontPanel( String s ) throws LoadException { + frontPanels.add( new Panel( s, loadImage( s ) ) ); + } + + public List getBackPanelOrder(){ + return new ArrayList( panelOrder ); + } + + public void addBackPanelOrder( String path ){ + for ( Iterator it = this.backPanels.entrySet().iterator(); it.hasNext(); ){ + Map.Entry entry = (Map.Entry) it.next(); + Integer key = (Integer) entry.getKey(); + Panel panel = (Panel) entry.getValue(); + if ( panel.name.equals( path ) ){ + panelOrder.add( key ); + return; + } + } + } + + public void removeLastOrder(){ + if ( ! panelOrder.isEmpty() ){ + panelOrder.remove( panelOrder.size() - 1 ); + } + } + + private Integer findFreeKey( HashMap map ){ + int i = 0; + for ( Iterator it = map.keySet().iterator(); it.hasNext(); ){ + Integer x = (Integer) it.next(); + if ( i <= x.intValue() ){ + i = x.intValue() + 1; + } + } + return new Integer( i ); + } + + public void addBackPanel( String path ) throws LoadException { + this.backPanels.put( findFreeKey( backPanels ), new Panel( path, loadImage( path ) ) ); + } + + public String getBackPanelName( int i ){ + Panel panel = (Panel) backPanels.get( new Integer( i ) ); + return panel.name; + } + + public void removeBackPanel( String path ){ + for ( Iterator it = this.backPanels.entrySet().iterator(); it.hasNext(); ){ + Map.Entry entry = (Map.Entry) it.next(); + Integer key = (Integer) entry.getKey(); + Panel panel = (Panel) entry.getValue(); + if ( panel.name.equals( path ) ){ + this.backPanels.remove( key ); + for ( Iterator orders = this.panelOrder.iterator(); orders.hasNext(); ){ + Integer o = (Integer) orders.next(); + if ( o.equals( key ) ){ + orders.remove(); + } + } + return; + } + } + } + + public List getFrontPanelNames(){ + List names = new ArrayList(); + for ( Iterator it = frontPanels.iterator(); it.hasNext(); ){ + Panel panel = (Panel) it.next(); + names.add( panel.name ); + } + return names; + } + + public List getBackPanelNames(){ + List names = new ArrayList(); + for ( Iterator it = backPanels.values().iterator(); it.hasNext(); ){ + Panel panel = (Panel) it.next(); + names.add( panel.name ); + } + return names; + } + + public void removeFrontPanel( String s ){ + for ( Iterator it = frontPanels.iterator(); it.hasNext(); ){ + Panel panel = (Panel) it.next(); + if ( panel.name.equals( s ) ){ + frontPanels.remove( panel ); + break; + } + } + } + + private void drawFrontPanels( Graphics2D g ){ + int w = 0; + if ( ! frontPanels.isEmpty() ){ + while ( w < getWidth() / getScale() ){ + int ow = w; + for ( Iterator it = frontPanels.iterator(); it.hasNext(); ){ + Panel p = (Panel) it.next(); + Image panel = (Image) p.image; + g.drawImage( panel, w, 0, null ); + w += panel.getWidth( null ); + } + /* make sure not to get into an infinite loop due to lack + * of width on the images + */ + if ( w == ow ){ + w += 1; + } + } + } + } + + private void drawBackground( Graphics2D g ){ + if ( background != null ){ + int w = 0; + while ( w < getWidth() / getScale() ){ + g.drawImage( background, w, 0, null ); + w += background.getWidth( null ); + } + } + } + + private void drawBackPanels( Graphics2D g ){ + int w = 0; + for ( Iterator it = this.panelOrder.iterator(); it.hasNext(); ){ + Integer i = (Integer) it.next(); + Image image = ((Panel) this.backPanels.get( i )).image; + g.drawImage( image, w, 0, null ); + w += image.getWidth( null ); + } + } + + private void drawBlocks( Graphics2D g, int height ){ + int w = 0; + int num = 1; + for ( Iterator it = this.blocks.iterator(); it.hasNext(); ){ + Block b = (Block) it.next(); + if ( b.isEnabled() ){ + b.render( g, w, height, minZ, maxZ, num ); + g.drawString( "Block " + num, w + 5, 15 ); + w += b.getLength(); + } + num += 1; + } + } + + public void render( Graphics2D g, int x, int y, int width, int height ){ + // g.clearRect( 0, 0, (int) getWidth(), (int) getHeight() ); + g.scale( getScale(), getScale() ); + drawBackground( g ); + drawBackPanels( g ); + g.setColor( new Color( 255, 0, 0 ) ); + g.drawLine( 0, getMinZ(), (int)(getWidth() / getScale()), getMinZ() ); + g.drawLine( 0, getMaxZ(), (int)(getWidth() / getScale()), getMaxZ() ); + drawBlocks( g, height ); + drawFrontPanels( g ); + } + + public Block findBlock( Thing t ){ + for ( Iterator it = blocks.iterator(); it.hasNext(); ){ + Block b = (Block) it.next(); + if ( b.hasThing( t ) ){ + return b; + } + } + return null; + } + + /* find the block that contains point x */ + public Block findBlock( int x ){ + int total = 0; + for ( Iterator it = getBlocks().iterator(); it.hasNext(); ){ + Block b = (Block) it.next(); + if ( b.isEnabled() ){ + if ( x >= total && x <= total + b.getLength() ){ + return b; + } + total += b.getLength(); + } + } + return null; + } + + public Thing findThing( int x, int y ){ + for ( Iterator it = blocks.iterator(); it.hasNext(); ){ + Block b = (Block) it.next(); + if ( b.isEnabled() ){ + Thing t = b.findThing( x, y - getMinZ() ); + if ( t != null ){ + return t; + } + x -= b.getLength(); + } + } + return null; + } + + public void moveThing( Thing thing, int x, int y ){ + for ( Iterator it = blocks.iterator(); it.hasNext(); ){ + Block b = (Block) it.next(); + if ( b.isEnabled() ){ + if ( b.hasThing( thing ) ){ + int my = y - getMinZ(); + if ( my > getMaxZ() - getMinZ() ){ + my = getMaxZ() - getMinZ(); + } + if ( my < 0 ){ + my = 0; + } + // System.out.println( b + ". Move " + thing + " to " + x + " " + my ); + thing.setX( x ); + thing.setY( my ); + } else { + // System.out.println( b + " does not have " + thing ); + } + // x -= b.getLength(); + } + } + } + + public void initAll(){ + this.name = null; + this.background = null; + this.backgroundFile = null; + this.minZ = 100; + this.maxZ = 200; + this.scale = 2; + this.backgroundParallax = 5; + this.foregroundParallax = 1.2; + this.atmosphere = null; + + this.width = 640; + this.frontPanels = new ArrayList(); + this.backPanels = new HashMap(); + this.panelOrder = new ArrayList(); + this.blocks = new ArrayList(); + + resetId(); + resetBlockId(); + } + + private void load( File f ) throws LoadException { + initAll(); + TokenReader reader = new TokenReader( f ); + Token head = reader.nextToken(); + if ( ! head.getName().equals( "level" ) ){ + throw new LoadException( "Starting token is not 'level'" ); + } + + Token z = head.findToken( "z" ); + if ( z != null ){ + Token min = z.findToken( "minimum" ); + if ( min != null ){ + minZ = min.readInt( 0 ); + } + Token max = z.findToken( "maximum" ); + if ( max != null ){ + maxZ = max.readInt( 0 ); + } + } + + Token desc = head.findToken("description"); + if (desc != null){ + setDescription(desc.readString(0)); + } + + Token introToken = head.findToken("intro"); + if (introToken != null){ + setIntro(introToken.readString(0)); + } + + Token endingToken = head.findToken("ending"); + if (endingToken != null){ + setEnding(endingToken.readString(0)); + } + + trigger = head.findToken("trigger"); + + Token atm = head.findToken( "atmosphere" ); + if ( atm != null ){ + setAtmosphere( atm.readString( 0 ) ); + } + + Token parallax = head.findToken( "background-parallax" ); + if ( parallax != null ){ + setBackgroundParallax( parallax.readDouble( 0 ) ); + } + parallax = head.findToken( "foreground-parallax" ); + if ( parallax != null ){ + setForegroundParallax( parallax.readDouble( 0 ) ); + } + + loadBackground( head.findToken( "background" ) ); + + // setWidth( calculateWidth( head.findTokens( "block/length" ) ) ); + for ( Iterator it = head.findTokens( "frontpanel" ).iterator(); it.hasNext(); ){ + Token t = (Token) it.next(); + String file = t.readString( 0 ); + frontPanels.add( new Panel( file, loadImage( file ) ) ); + } + + for ( Iterator it = head.findTokens( "panel" ).iterator(); it.hasNext(); ){ + Token t = (Token) it.next(); + int index = t.readInt( 0 ); + String file = t.readString( 1 ); + this.backPanels.put( new Integer( index ), new Panel( file, loadImage( file ) ) ); + } + + Token order = head.findToken( "order" ); + if ( order != null ){ + for ( Iterator it = order.iterator(); it.hasNext(); ){ + panelOrder.add( Integer.valueOf( it.next().toString() ) ); + } + } else { + System.out.println( "No 'order' token given" ); + } + + for ( Iterator it = head.findTokens( "block" ).iterator(); it.hasNext(); ){ + Token t = (Token) it.next(); + this.blocks.add( new Block( t ) ); + } + + System.out.println( "Loaded " + f ); + } + + private Image loadImage( String s ) throws LoadException { + try{ + return MaskedImage.load( Editor.dataPath( s ) ); + } catch ( IOException ie ){ + throw new LoadException( "Could not load " + s ); + } + } + + private void loadBackground( Token t ) throws LoadException { + if ( t != null ){ + String s = String.valueOf( t.iterator().next() ); + setBackground( loadImage( s ) ); + backgroundFile = s; + } + } + + public void loadBackground( String s ){ + try{ + setBackground( loadImage( s ) ); + backgroundFile = s; + } catch ( LoadException e ){ + e.printStackTrace(); + } + } + + public String getBackgroundFile(){ + return backgroundFile; + } + + private void setWidth( int w ){ + width = w; + } + + private int calculateWidth( List lengths ){ + int w = 0; + for ( Iterator it = lengths.iterator(); it.hasNext(); ){ + Token t = (Token) it.next(); + w += t.readInt( 0 ) * getScale(); + } + return w; + } + + private void setBackground( Image i ){ + background = i; + } + + public Dimension getSize(){ + return new Dimension( (int) getWidth(), (int) getHeight() ); + } + + public double getWidth(){ + double w = 0; + for ( Iterator it = blocks.iterator(); it.hasNext(); ){ + Block b = (Block) it.next(); + if ( b.isEnabled() ){ + w += b.getLength() * getScale(); + } + } + return w; + } + + public Token toToken(){ + Token level = new Token(); + level.addToken( new Token( level, "level" ) ); + Token z = new Token( level ); + level.addToken( z ); + z.addToken( new Token( z, "z" ) ); + z.addToken( new String[]{ "minimum", String.valueOf( getMinZ() ) } ); + z.addToken( new String[]{ "maximum", String.valueOf( getMaxZ() ) } ); + if ( getAtmosphere() != null ){ + level.addToken( new String[]{ "atmosphere", getAtmosphere() } ); + } + level.addToken( new String[]{ "background-parallax", String.valueOf( getBackgroundParallax() ) } ); + level.addToken( new String[]{ "foreground-parallax", String.valueOf( getForegroundParallax() ) } ); + + if (getIntro() != ""){ + level.addToken(new String[]{"intro", getIntro()}); + } + + if (getEnding() != ""){ + level.addToken(new String[]{"ending", getEnding()}); + } + + if ( backgroundFile != null ){ + level.addToken( new String[]{ "background", "\"" + backgroundFile.replaceAll( "\\\\", "/" ) + "\"" } ); + } + + for ( Iterator it = frontPanels.iterator(); it.hasNext(); ){ + /* + Token f = new Token( level ); + level.addToken( f ); + Panel p = (Panel) it.next(); + f.addToken( new Token( f, "frontpanel" ) ); + f.addToken( new Token( f, p.name ) ); + */ + Panel p = (Panel) it.next(); + level.addToken( new String[]{ "frontpanel", "\"" + p.name.replaceAll( "\\\\", "/" ) + "\"" } ); + } + + for ( Iterator it = backPanels.entrySet().iterator(); it.hasNext(); ){ + Map.Entry entry = (Map.Entry) it.next(); + Token f = new Token( level ); + level.addToken( f ); + f.addToken( new Token( f, "panel" ) ); + Panel p = (Panel) entry.getValue(); + f.addToken( new Token( f, entry.getKey().toString() ) ); + f.addToken( new Token( f, "\"" + p.name.replaceAll( "\\\\", "/" ) + "\"" ) ); + } + + if (getDescription() != null){ + level.addToken(new String[]{ "description", "\"" + getDescription() + "\""}); + } + + if (trigger != null){ + level.addToken(trigger); + } + + Token order = new Token( level ); + level.addToken( order ); + order.addToken( new Token( order, "order" ) ); + for ( Iterator it = panelOrder.iterator(); it.hasNext(); ){ + order.addToken( new Token( order, it.next().toString() ) ); + } + + for ( Iterator it = blocks.iterator(); it.hasNext(); ){ + Block b = (Block) it.next(); + level.addToken( b.toToken() ); + } + + return level; + } + + public double getScale(){ + return scale; + } + + public void setScale( double s ){ + scale = s; + } + + public double getHeight(){ + return 240 * getScale(); + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/level/objects/Stimulation.java b/editor/src/main/java/com/rafkind/paintown/level/objects/Stimulation.java new file mode 100644 index 000000000..0f3f2dbd2 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/level/objects/Stimulation.java @@ -0,0 +1,148 @@ +package com.rafkind.paintown.level.objects; + +import com.rafkind.paintown.exception.LoadException; +import com.rafkind.paintown.Token; + +public abstract class Stimulation{ + public Stimulation(){ + } + + public static Stimulation load(Token t) throws LoadException { + Token health = t.findToken("health"); + if (health != null){ + return new HealthStimulation(health); + } + Token speed = t.findToken("speed"); + if (speed != null){ + return new SpeedStimulation(speed); + } + Token invincibility = t.findToken("invincibility"); + if (invincibility != null){ + return new InvincibilityStimulation(invincibility); + } + + return null; + } + + public abstract Stimulation copy(); + public abstract Token toToken(); + + public static class HealthStimulation extends Stimulation { + private int health; + + public HealthStimulation(){ + } + + public HealthStimulation(Token t) throws LoadException { + health = t.readInt(0); + } + + public HealthStimulation(HealthStimulation copy){ + health = copy.health; + } + + public int getHealth(){ + return health; + } + + public void setHealth( int s ){ + health = s; + } + + public Stimulation copy(){ + return new HealthStimulation(this); + } + + public Token toToken(){ + Token t = new Token(); + + t.addToken(new Token("stimulation")); + t.addToken(new String[]{"health", String.valueOf(health)}); + + return t; + } + } + + public static class InvincibilityStimulation extends Stimulation { + private int duration; + + public InvincibilityStimulation(){ + } + + public InvincibilityStimulation(Token t) throws LoadException { + duration = t.readInt(0); + } + + public InvincibilityStimulation(InvincibilityStimulation copy){ + duration = copy.duration; + } + + public int getDuration(){ + return duration; + } + + public void setDuration(int duration){ + this.duration = duration; + } + + public Stimulation copy(){ + return new InvincibilityStimulation(this); + } + + public Token toToken(){ + Token t = new Token(); + + t.addToken(new Token("stimulation")); + t.addToken(new String[]{"invincibility", String.valueOf(duration)}); + + return t; + } + } + + public static class SpeedStimulation extends Stimulation { + private double boost; + private int ticks; + + public SpeedStimulation(){ + } + + public SpeedStimulation(Token t) throws LoadException { + boost = t.readDouble(0); + ticks = t.readInt(1); + } + + public SpeedStimulation(SpeedStimulation copy){ + boost = copy.boost; + ticks = copy.ticks; + } + + public double getBoost(){ + return boost; + } + + public int getTicks(){ + return ticks; + } + + public void setBoost(double boost){ + this.boost = boost; + } + + public void setTicks(int ticks){ + this.ticks = ticks; + } + + public Stimulation copy(){ + return new SpeedStimulation(this); + } + + public Token toToken(){ + Token t = new Token(); + + t.addToken(new Token("stimulation")); + t.addToken(new String[]{"speed", String.valueOf(boost), String.valueOf(ticks)}); + + return t; + } + } +} diff --git a/editor/src/main/java/com/rafkind/paintown/level/objects/Thing.java b/editor/src/main/java/com/rafkind/paintown/level/objects/Thing.java new file mode 100644 index 000000000..7988b91b1 --- /dev/null +++ b/editor/src/main/java/com/rafkind/paintown/level/objects/Thing.java @@ -0,0 +1,213 @@ +package com.rafkind.paintown.level.objects; + +import com.rafkind.paintown.exception.LoadException; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.TokenReader; +import com.rafkind.paintown.MaskedImage; +import com.rafkind.paintown.level.PropertyEditor; +import com.rafkind.paintown.Lambda1; +import com.rafkind.paintown.level.Editor; + +import java.io.*; +import java.awt.*; +import java.awt.image.*; +import java.awt.geom.*; +import javax.imageio.*; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.ArrayList; +import java.util.List; + +public abstract class Thing { + private int x, y; + private int id; + private int width, height; + private String path; + private BufferedImage main; + private String name; + private boolean selected; + private static HashMap images = new HashMap(); + + private List listeners; + + public Thing( Token token ) throws LoadException { + Token coords = token.findToken( "coords" ); + if ( coords != null ){ + x = coords.readInt( 0 ); + y = coords.readInt( 1 ); + } + + Token tid = token.findToken("id"); + if (tid != null){ + id = tid.readInt(0); + Level.checkId(id); + } else { + id = Level.nextId(); + } + + Token tpath = token.findToken( "path" ); + if ( tpath != null ){ + setPath( tpath.readString( 0 ) ); + main = loadImage( Editor.dataPath( getPath() ), this ); + } + + listeners = new ArrayList(); + } + + public Thing( Thing copy ){ + listeners = new ArrayList(); + setX( copy.getX() ); + setY( copy.getY() ); + setId(copy.getId()); + setMain( copy.getMain() ); + setPath( copy.getPath() ); + setName( copy.getName() ); + } + + private void setMain( BufferedImage image ){ + main = image; + } + + public BufferedImage getMain(){ + return main; + } + + public void setName( String s ){ + this.name = s; + fireUpdate(); + } + + public int getId(){ + return id; + } + + public void setId(int i){ + id = i; + } + + public abstract Thing copy(); + + public String getName(){ + return name; + } + + public String toString(){ + return "X: " + getX() + " Y: " + getY() + " Name: " + getName(); + } + + private void setPath( String p ){ + path = p; + } + + public String getPath(){ + return path.replaceAll( "\\\\", "/" ); + } + + public abstract PropertyEditor getEditor(); + + public int getY(){ + return y; + } + + public int getX(){ + return x; + } + + public void setY( int y ){ + // this.y = y + main.getHeight( null ) / 2; + this.y = y; + fireUpdate(); + } + + public void setX( int x ){ + this.x = x; + fireUpdate(); + } + + private void fireUpdate(){ + for ( Iterator it = listeners.iterator(); it.hasNext(); ){ + Lambda1 proc = (Lambda1) it.next(); + proc.invoke_( this ); + } + } + + public void addListener( Lambda1 proc ){ + if ( ! listeners.contains( proc ) ){ + listeners.add( proc ); + } + } + + public void removeListener( Lambda1 proc ){ + listeners.remove( proc ); + } + + public int getWidth(){ + return main.getWidth( null ); + } + + public int getX1(){ + return getX() - main.getWidth( null ) / 2; + } + + public int getY1(){ + return getY() - main.getHeight( null ); + } + + public int getX2(){ + return getX() + main.getWidth( null ) / 2; + } + + public int getY2(){ + return getY(); + } + + public void setSelected( boolean s ){ + selected = s; + } + + protected void render( Image look, Graphics2D g, boolean highlight ){ + // g.drawImage( main, startX + x, y, null ); + // int mx = startX + x - main.getWidth( null ) / 2; + // int mx = startX + x + main.getWidth( null ) / 2; + int mx = x + look.getWidth( null ) / 2; + int my = y - look.getHeight( null ); + g.drawImage( look, new AffineTransform( -1, 0, 0, 1, mx, my ), null ); + if ( selected ){ + g.setColor( new Color( 0x66, 0xff, 0x77 ) ); + } else if ( highlight ){ + // g.setColor( new Color( 0x66, 0xff, 0x22 ) ); + g.setColor( new Color( 0xff, 0x44, 0x33 ) ); + } else { + g.setColor( new Color( 64, 64, 255 ) ); + } + g.drawRect( getX1(), getY1(), look.getWidth( null ), look.getHeight( null ) ); + g.setColor( new Color( 255, 255, 255 ) ); + g.fillOval( x, y, 5, 5 ); + + } + + public void render( Graphics2D g, boolean highlight ){ + render( getMain(), g, highlight ); + } + + private static BufferedImage loadImage( String file, Thing t ) throws LoadException { + if ( images.get( file ) != null ){ + return (BufferedImage) images.get( file ); + } + images.put( file, t.readIdleImage( file ) ); + return loadImage( file, t ); + } + + protected abstract BufferedImage readIdleImage( String file ) throws LoadException; + protected abstract String getType(); + public abstract Token toToken(); + + public boolean equals( Object t ){ + return this == t; + } + + public int hashCode(){ + return toToken().toString().hashCode(); + } +} diff --git a/editor/src/main/scala/com/rafkind/paintown/animator/Animator2.scala b/editor/src/main/scala/com/rafkind/paintown/animator/Animator2.scala new file mode 100644 index 000000000..0a67ddfc5 --- /dev/null +++ b/editor/src/main/scala/com/rafkind/paintown/animator/Animator2.scala @@ -0,0 +1,741 @@ +package com.rafkind.paintown.animator + +import java.io._ +import java.awt +import javax.swing +import java.util.regex.Pattern + +import org.swixml.SwingEngine; +import com.rafkind.paintown.exception._ + +import com.rafkind.paintown._ + +class NewAnimator extends swing.JFrame("Paintown Animator"){ + + val propertyLastLoaded = "animator:last-loaded" + /* Define it here so we can modify it in loadPlayer */ + val loadLastCharacter = new swing.JMenuItem("Load last character") + + def get[T](list:List[T], index:Int):T = { + list.find(list.indexOf(_) == index) match { + case Some(obj) => obj + case None => throw new Exception("failed to find " + index) + } + } + + def toScalaList[T](list:java.util.List[T]):List[T] = { + var out:List[T] = List[T]() + for (item <- scala.collection.JavaConversions.asScalaBuffer(list)){ + out = out :+ item + } + out + } + + val pane = new swing.JTabbedPane() + + def construct(){ + val screen = awt.Toolkit.getDefaultToolkit().getScreenSize() + this.setSize((screen.getWidth() * 0.9).toInt, + (screen.getHeight() * 0.9).toInt); + + Closer.open() + + val menuBar = new swing.JMenuBar() + val menuProgram = new swing.JMenu("Program") + val undo = new swing.JMenuItem("Undo last action"); + // val redo = new swing.JMenuItem("Redo last undo"); + val levelEditor = new swing.JMenuItem("Run the level editor") + val quit = new swing.JMenuItem("Quit") + val clearCache = new swing.JMenuItem("Clear image cache") + val data = new swing.JMenuItem("Data path") + val gameSpeed = new swing.JMenuItem("Game ticks") + val closeTab = new swing.JMenuItem("Close Tab") + menuProgram.add(levelEditor) + menuProgram.add(undo) + // menuProgram.add(redo) + menuProgram.add(data) + menuProgram.add(gameSpeed) + menuProgram.add(clearCache) + menuProgram.add(closeTab) + menuProgram.add(quit) + menuBar.add(menuProgram) + + Undo.setUndoMenuItem(undo) + + val menuProjectile = new swing.JMenu("Projectile") + menuBar.add(menuProjectile) + + val newProjectile = new swing.JMenuItem("New Projectile") + menuProjectile.add(newProjectile) + val openProjectile = new swing.JMenuItem("Open Projectile") + menuProjectile.add(openProjectile) + val saveProjectile = new swing.JMenuItem("Save Projectile") + menuProjectile.add(saveProjectile) + + val menuCharacter = new swing.JMenu("Character") + menuBar.add(menuCharacter) + + val newCharacter = new swing.JMenuItem("New Character") + menuCharacter.add(newCharacter) + val loadCharacter = new swing.JMenuItem("Open Character") + menuCharacter.add(loadCharacter) + menuCharacter.add(loadLastCharacter) + val saveCharacter = new swing.JMenuItem("Save Character") + menuCharacter.add(saveCharacter) + val saveCharacterAs = new swing.JMenuItem("Save Character As") + menuCharacter.add(saveCharacterAs) + + undo.setMnemonic(awt.event.KeyEvent.VK_U) + undo.setAccelerator(swing.KeyStroke.getKeyStroke(awt.event.KeyEvent.VK_Z, awt.Event.CTRL_MASK)) + + // redo.setMnemonic(awt.event.KeyEvent.VK_R) + // redo.setAccelerator(swing.KeyStroke.getKeyStroke(awt.event.KeyEvent.VK_R, awt.Event.CTRL_MASK)) + + menuProgram.setMnemonic(awt.event.KeyEvent.VK_P) + data.setMnemonic(awt.event.KeyEvent.VK_D) + closeTab.setMnemonic(awt.event.KeyEvent.VK_C) + closeTab.setAccelerator(swing.KeyStroke.getKeyStroke(awt.event.KeyEvent.VK_W, awt.Event.CTRL_MASK)) + quit.setMnemonic(awt.event.KeyEvent.VK_Q) + quit.setAccelerator(swing.KeyStroke.getKeyStroke(awt.event.KeyEvent.VK_Q, awt.Event.CTRL_MASK)) + newCharacter.setMnemonic(awt.event.KeyEvent.VK_N) + newCharacter.setAccelerator(swing.KeyStroke.getKeyStroke(awt.event.KeyEvent.VK_N, awt.Event.CTRL_MASK)) + menuCharacter.setMnemonic(awt.event.KeyEvent.VK_H) + saveCharacter.setMnemonic(awt.event.KeyEvent.VK_S) + saveCharacter.setAccelerator(swing.KeyStroke.getKeyStroke(awt.event.KeyEvent.VK_S, awt.Event.CTRL_MASK)) + saveCharacterAs.setMnemonic(awt.event.KeyEvent.VK_A) + saveCharacterAs.setAccelerator(swing.KeyStroke.getKeyStroke(awt.event.KeyEvent.VK_A, awt.Event.CTRL_MASK)) + loadCharacter.setMnemonic(awt.event.KeyEvent.VK_O) + loadCharacter.setAccelerator(swing.KeyStroke.getKeyStroke(awt.event.KeyEvent.VK_O, awt.Event.CTRL_MASK)) + + this.setJMenuBar(menuBar) + this.addWindowListener(new awt.event.WindowAdapter(){ + override def windowClosing(e:awt.event.WindowEvent){ + Closer.close(); + } + }) + + Config.getConfig().get(propertyLastLoaded).asInstanceOf[String] match { + case null => {} + case what => loadLastCharacter.setText("Load last character " + new File(what).getName()) + } + + loadLastCharacter.addActionListener(new swing.AbstractAction(){ + override def actionPerformed(event:awt.event.ActionEvent){ + val path = Config.getConfig().get(propertyLastLoaded).asInstanceOf[String] + if (path != null){ + loadPlayer(new File(path)) + } + } + }) + + undo.addActionListener(new swing.AbstractAction(){ + override def actionPerformed(event:awt.event.ActionEvent){ + Undo.popUndo() + } + }) + + /* + redo.addActionListener(new swing.AbstractAction(){ + override def actionPerformed(event:awt.event.ActionEvent){ + Undo.popRedo() + } + }) + */ + + levelEditor.addActionListener(new swing.AbstractAction(){ + override def actionPerformed(event:awt.event.ActionEvent){ + com.rafkind.paintown.level.Editor2.main(new Array[String](0)); + } + }); + + quit.addActionListener(new awt.event.ActionListener() { + override def actionPerformed(event:awt.event.ActionEvent){ + Closer.close() + } + }); + + clearCache.addActionListener(new awt.event.ActionListener(){ + override def actionPerformed(event:awt.event.ActionEvent){ + MaskedImage.clearCache(); + } + }); + + val quickEngine = new SwingEngine("animator/quick.xml"); + val quickDisplay = quickEngine.find("display").asInstanceOf[swing.JLabel]; + val quickDisplayIcon = quickDisplay.getIcon(); + // quickDisplay.setIcon(quickDisplayIcon); + + class QuickCharacterLoaderModel extends swing.ListModel[File] { + var listeners:List[swing.event.ListDataListener] = List() + var data:List[File] = List() + var filtered:List[File] = List() + var filter:Pattern = Pattern.compile(".*") + + load(Data.getDataPath()) + + def load(path:File){ + val self = this + data = List() + new swing.SwingWorker[List[File], File]{ + override def doInBackground():List[File] = { + val filter = new FilenameFilter(){ + override def accept(dir:File, name:String):Boolean = { + val up = dir.getName(); + // System.out.println("Maybe file " + up + "/" + name); + return !dir.getName().equals(".svn") && + (new File(dir, name).isDirectory() || + name.endsWith(".txt")); + } + }; + + def find(directory:File){ + val files = directory.listFiles(filter); + for (file <- files){ + if (file.isDirectory()){ + find(file) + } else { + publish(file) + } + } + } + + find(path) + return List() + } + + override def done(){ + quickDisplay.setIcon(null) + } + + override def process(files:java.util.List[File]) = { + self.add(toScalaList(files)) + } + }.execute() + } + + def modificationCompare(file1:File, file2:File):Boolean = { + file1.lastModified() < file2.lastModified() + } + + def add(files:List[File]){ + data = data ++ files + updateView(filter) + } + + def refilter(pattern:Pattern):List[File] = { + data.filter((file) => pattern.matcher(file.getCanonicalPath()).matches).sortWith(modificationCompare) + } + + def updateView(filter:Pattern){ + this.filtered = refilter(filter); + val event = new swing.event.ListDataEvent(this, swing.event.ListDataEvent.INTERVAL_ADDED, 0, filtered.size); + for (listener <- listeners){ + listener.intervalAdded(event); + } + } + + override def addListDataListener(listener:swing.event.ListDataListener){ + listeners = listeners :+ listener + } + + override def getElementAt(index:Int) = { + get(filtered, index) + // return this.filtered.get(index); + } + + override def getSize():Int = { + this.filtered.size + } + + override def removeListDataListener(listener:swing.event.ListDataListener){ + listeners = listeners diff List(listener) + } + + def setFilter(input:String){ + this.filter = Pattern.compile(".*" + input + ".*"); + updateView(filter); + } + } + + val quickLoaderModel = new QuickCharacterLoaderModel(); + + val quickFilter = quickEngine.find("filter").asInstanceOf[swing.JTextField]; + quickFilter.getDocument().addDocumentListener(new swing.event.DocumentListener(){ + override def changedUpdate(event:swing.event.DocumentEvent){ + quickLoaderModel.setFilter(quickFilter.getText()); + } + + override def insertUpdate(event:swing.event.DocumentEvent){ + quickLoaderModel.setFilter(quickFilter.getText()); + } + + override def removeUpdate(event:swing.event.DocumentEvent){ + quickLoaderModel.setFilter(quickFilter.getText()); + } + }); + + val quickLoader = quickEngine.find("list").asInstanceOf[swing.JList[File]]; + + def quickDoLoad(){ + val files = quickLoader.getSelectedValues().asInstanceOf[Array[Object]]; + for (file_ <- files){ + val file = file_.asInstanceOf[File] + new Thread(new Runnable(){ + def run(){ + if (isPlayerFile(file)){ + loadPlayer(file) + } else if (isProjectileFile(file)){ + loadProjectile(file) + } + } + }).start(); + } + } + + quickLoader.setModel(quickLoaderModel); + quickLoader.addMouseListener(new awt.event.MouseAdapter(){ + override def mouseClicked(event:awt.event.MouseEvent){ + if (event.getClickCount() == 2){ + quickDoLoad() + } + } + }); + + quickLoader.getInputMap().put(swing.KeyStroke.getKeyStroke(awt.event.KeyEvent.VK_ENTER, 0), "open"); + quickLoader.getInputMap().put(swing.KeyStroke.getKeyStroke(awt.event.KeyEvent.VK_A, 2), "select-all"); + quickLoader.getActionMap().put("open", new swing.AbstractAction(){ + override def actionPerformed(event:awt.event.ActionEvent){ + quickDoLoad() + } + }); + + quickLoader.getActionMap().put("select-all", new swing.AbstractAction(){ + override def actionPerformed(event:awt.event.ActionEvent){ + val indicies:Array[Int] = new Array[Int](quickLoaderModel.getSize()) + for (index <- 0 to (quickLoaderModel.getSize() - 1)){ + indicies(index) = index + } + quickLoader.setSelectedIndices(indicies); + } + }); + + val quickLoadButton = quickEngine.find("load").asInstanceOf[swing.JButton]; + quickLoadButton.addActionListener(new swing.AbstractAction(){ + override def actionPerformed(event:awt.event.ActionEvent){ + quickDoLoad() + } + }); + + val quickLoaderPane = quickEngine.getRootComponent().asInstanceOf[swing.JPanel] + + pane.add("Quick character loader", quickLoaderPane) + + getContentPane().add(pane) + + gameSpeed.addActionListener(new awt.event.ActionListener(){ + override def actionPerformed(event:awt.event.ActionEvent){ + val speed = new swing.JDialog(NewAnimator.this, "Change game speed"); + speed.setSize(200, 100); + val engine = new SwingEngine("animator/game-speed.xml"); + val spinner = engine.find("speed").asInstanceOf[swing.JSpinner] + + spinner.setValue(new Integer(NewAnimator.getTicksPerSecond())); + spinner.addChangeListener(new swing.event.ChangeListener(){ + override def stateChanged(event:swing.event.ChangeEvent){ + NewAnimator.setTicksPerSecond(spinner.getValue().asInstanceOf[Integer].intValue()) + } + }); + + speed.getContentPane().add(engine.getRootComponent().asInstanceOf[swing.JPanel]) + speed.setVisible(true) + } + }) + + data.addActionListener(new awt.event.ActionListener(){ + override def actionPerformed(event:awt.event.ActionEvent){ + /* just a container for an object */ + class ObjectBox{ + var internal:Object = null + def set(o:Object){ + internal = o + } + + def get():Object = { + return internal; + } + } + val engine = new SwingEngine("data-path.xml"); + val path = engine.find("path").asInstanceOf[swing.JTextField]; + val box = new ObjectBox(); + box.set(Data.getDataPath()); + path.setText( Data.getDataPath().getPath() ); + val change = engine.find("change").asInstanceOf[swing.JButton]; + change.addActionListener(new swing.AbstractAction(){ + override def actionPerformed(event:awt.event.ActionEvent){ + val chooser = new swing.JFileChooser(new File(".")); + chooser.setFileSelectionMode(swing.JFileChooser.DIRECTORIES_ONLY); + val returnVal = chooser.showOpenDialog(NewAnimator.this); + if (returnVal == swing.JFileChooser.APPROVE_OPTION){ + val newPath:File = chooser.getSelectedFile(); + path.setText(newPath.getPath()); + box.set(newPath); + } + } + }); + val save = engine.find("save").asInstanceOf[swing.JButton]; + val cancel = engine.find("cancel").asInstanceOf[swing.JButton]; + val dialog = new swing.JDialog(NewAnimator.this, "Paintown data path"); + save.addActionListener(new swing.AbstractAction(){ + override def actionPerformed(event:awt.event.ActionEvent){ + val path = box.get().asInstanceOf[File]; + Data.setDataPath(path); + quickLoaderModel.load(path); + dialog.setVisible(false); + } + }); + + cancel.addActionListener(new swing.AbstractAction(){ + override def actionPerformed(event:awt.event.ActionEvent){ + dialog.setVisible(false); + } + }); + + val panel = engine.getRootComponent().asInstanceOf[swing.JPanel]; + dialog.getContentPane().add(panel); + dialog.setSize(300, 300); + dialog.setVisible(true); + } + }); + + closeTab.addActionListener(new swing.AbstractAction(){ + override def actionPerformed(event:awt.event.ActionEvent){ + if (pane.getSelectedComponent() != quickLoaderPane){ + pane.remove(pane.getSelectedComponent()) + } + } + }); + + /* dont need this + pane.addChangeListener(new swing.event.ChangeListener(){ + override def stateChanged(changeEvent:swing.event.ChangeEvent){ + val sourceTabbedPane = changeEvent.getSource().asInstanceOf[JTabbedPane]; + val index = sourceTabbedPane.getSelectedIndex(); + CURRENT_TAB = index; + } + }); + */ + + newProjectile.addActionListener(new swing.AbstractAction(){ + override def actionPerformed(event:awt.event.ActionEvent){ + val projectile = new Projectile("New Projectile"); + val pane = new ProjectilePane(NewAnimator.this, projectile); + addNewTab(pane.getEditor(), projectile.getName()); + } + }); + + newCharacter.addActionListener(new swing.AbstractAction(){ + override def actionPerformed(event:awt.event.ActionEvent){ + val character = new CharacterStats("New Character"); + val pane = new Player(NewAnimator.this, character); + + addNewTab(pane.getEditor(), "New Character"); + } + }); + + openProjectile.addActionListener(new swing.AbstractAction(){ + override def actionPerformed(event:awt.event.ActionEvent){ + val chooser = new swing.JFileChooser(new File(".")); + chooser.setFileFilter(new swing.filechooser.FileFilter(){ + override def accept(file:File):Boolean = { + file.isDirectory() || file.getName().endsWith( ".txt" ); + } + + override def getDescription():String = { + "Projectile files"; + } + }); + + val returnVal = chooser.showOpenDialog(NewAnimator.this); + if (returnVal == swing.JFileChooser.APPROVE_OPTION){ + val file = chooser.getSelectedFile(); + try{ + val projectile = new Projectile(file.getName(), file); + projectile.setPath(file); + val pane = new ProjectilePane(NewAnimator.this, projectile); + addNewTab(pane.getEditor(), projectile.getName()); + } catch { + case fail:LoadException => { + //showError( "Could not load " + f.getName() ); + println("Could not load" + file.getName()) + fail.printStackTrace(); + } + } + } + } + }); + + loadCharacter.addActionListener(new swing.AbstractAction(){ + override def actionPerformed(event:awt.event.ActionEvent){ + val chooser = new swing.JFileChooser(new File(".")); + chooser.setFileFilter(new swing.filechooser.FileFilter(){ + override def accept(file:File):Boolean = { + file.isDirectory() || file.getName().endsWith(".txt"); + } + + override def getDescription():String = { + return "Character files (*.txt)"; + } + }); + + val returnVal = chooser.showOpenDialog(NewAnimator.this); + if (returnVal == swing.JFileChooser.APPROVE_OPTION){ + val file = chooser.getSelectedFile(); + loadPlayer(file) + /* + try{ + val character = new CharacterStats("New Character", file); + val tempPlayer = new Player(NewAnimator.this, character); + addNewTab(tempPlayer.getEditor(), character.getName()); + } catch { + case fail:LoadException => { + //showError( "Could not load " + f.getName() ); + println("Could not load " + file.getName()); + fail.printStackTrace(); + } + } + */ + } + } + }); + + def saveObject(obj:BasicObject, path:File){ + obj.setPath(path); + try{ + obj.saveData(); + doMessagePopup("Saved to " + path); + } catch { + case fail:Exception => { + doMessagePopup("Could not save:" + fail.getMessage()); + println(fail) + } + } + } + + def doSave(forceSelect:Boolean){ + if (pane.getSelectedComponent() != null){ + val basic = pane.getSelectedComponent().asInstanceOf[SpecialPanel].getObject() + if (basic != null){ + var file:File = null + if (!forceSelect){ + file = basic.getPath() + } + if (file == null){ + file = userSelectFile() + } + + /* write the text to a file */ + if (file != null){ + saveObject(basic, file) + } + } + } + } + + saveProjectile.addActionListener(new swing.AbstractAction(){ + override def actionPerformed(event:awt.event.ActionEvent){ + doSave(false) + } + }) + + saveCharacter.addActionListener(new swing.AbstractAction(){ + override def actionPerformed(event:awt.event.ActionEvent){ + doSave(false) + } + }); + + saveCharacterAs.addActionListener(new swing.AbstractAction(){ + override def actionPerformed(event:awt.event.ActionEvent){ + doSave(true) + } + }); + } + + construct() + + def isPlayerFile(file:File):Boolean = { + try{ + val reader = new TokenReader(file) + return reader.nextToken().getName() == "character" + } catch { + case le:LoadException => { + return false + } + } + } + + def isProjectileFile(file:File):Boolean = { + try{ + val reader = new TokenReader(file) + return reader.nextToken().getName() == "projectile" + } catch { + case le:LoadException => { + return false + } + } + } + + def loadProjectile(file:File) = { + try{ + println("Loading projectile " + file) + val projectile = new Projectile(file.getName(), file) + val pane = new ProjectilePane(NewAnimator.this, projectile); + swing.SwingUtilities.invokeLater(new Runnable(){ + def run(){ + addNewTab(pane.getEditor(), projectile.getName()); + } + }) + } catch { + case le:LoadException => { + //showError( "Could not load " + f.getName() ); + System.out.println("Could not load " + file.getName()); + le.printStackTrace(); + } + } + } + + def loadPlayer(file:File) = { + try{ + println("Loading character " + file) + val character = new CharacterStats("", file); + val tempPlayer = new Player(NewAnimator.this, character); + Config.getConfig().set(propertyLastLoaded, file.getAbsolutePath()) + loadLastCharacter.setText("Load last character " + file.getName()) + swing.SwingUtilities.invokeLater(new Runnable(){ + def run(){ + addNewTab(tempPlayer.getEditor(), character.getName()); + } + }) + } catch { + case le:LoadException => { + //showError( "Could not load " + f.getName() ); + System.out.println("Could not load " + file.getName()); + le.printStackTrace(); + } + } + } + + def addNewTab(panel:SpecialPanel, name:String){ + swing.SwingUtilities.invokeLater(new Runnable(){ + override def run(){ + pane.add(name, panel); + pane.setSelectedIndex(pane.getTabCount()-1); + + val tempPanel = panel + if (tempPanel.getTextBox() != null){ + panel.getTextBox().getDocument().addDocumentListener(new swing.event.DocumentListener(){ + override def changedUpdate(event:swing.event.DocumentEvent){ + pane.setTitleAt(pane.indexOfComponent(tempPanel), tempPanel.getTextBox().getText()); + } + + override def insertUpdate(event:swing.event.DocumentEvent){ + pane.setTitleAt(pane.indexOfComponent(tempPanel), tempPanel.getTextBox().getText()); + } + + override def removeUpdate(event:swing.event.DocumentEvent){ + pane.setTitleAt(pane.indexOfComponent(tempPanel), tempPanel.getTextBox().getText()); + } + }); + } + } + }); + } + + def doMessagePopup(message:String){ + val here = this.getLocation(); + swing.SwingUtilities.convertPointToScreen(here, this) + val x = (here.getX() + this.getWidth() / 3).toInt + val y = (here.getY() + this.getHeight() / 3).toInt + val label = new swing.JLabel(message); + label.setBackground(new awt.Color(0,43,250)); + label.setBorder(swing.BorderFactory.createEtchedBorder(swing.border.EtchedBorder.LOWERED)); + val popup = swing.PopupFactory.getSharedInstance().getPopup(this, label, x, y ); + popup.show(); + val kill = new swing.Timer(1000, new awt.event.ActionListener(){ + override def actionPerformed(event:awt.event.ActionEvent){ + } + }); + kill.addActionListener(new awt.event.ActionListener(){ + override def actionPerformed(event:awt.event.ActionEvent){ + popup.hide(); + kill.stop(); + } + }); + kill.start(); + } + + def userSelectFile():File = { + val chooser = new swing.JFileChooser(new File(".")) + val returnVal = chooser.showOpenDialog(NewAnimator.this) + if (returnVal == swing.JFileChooser.APPROVE_OPTION){ + chooser.getSelectedFile() + } else { + null + } + } +} + +object NewAnimator extends swing.JFrame("Paintown Animator"){ + /* The paintown engine uses 90 ticks per second by default */ + var ticksPerSecond = 90 + var animator:NewAnimator = null + def dataPath(f:File):File = { + new File(Data.getDataPath().getPath() + "/" + f.getPath()); + } + + def setTicksPerSecond(ticks:Int){ + ticksPerSecond = ticks + } + + def getTicksPerSecond() = ticksPerSecond + + def getNewFileChooser(title:String):RelativeFileChooser = + new RelativeFileChooser(animator, title, Data.getDataPath()) + + def getNewFileChooser(title:String, path:File):RelativeFileChooser = + new RelativeFileChooser(animator, title, Data.getDataPath(), path); + + def getFiles(path:String):List[String] = { + val dir = dataPath(new File(path)); + var files = List[String]() + /* use a FileFilter here */ + if (dir.isDirectory()){ + val all = dir.listFiles() + for (file <- all){ + if (file.getName().endsWith(".png") || + file.getName().endsWith(".tga") || + file.getName().endsWith(".pcx") || + file.getName().endsWith(".bmp")){ + files = files :+ file.getName() + } + } + } + files.sortWith((path1, path2) => (path1 compareTo path2) < 0) + } + +} + +object Animator2{ + def main(args: Array[String]):Unit = { + val editor = new NewAnimator() + System.out.println("Current working directory is " + System.getProperty("user.dir")) + NewAnimator.animator = editor + swing.SwingUtilities.invokeLater(new Runnable(){ + def run(){ + editor.setVisible(true); + for (arg <- args){ + editor.loadPlayer(new File(arg)) + } + } + }); + } +} diff --git a/editor/src/main/scala/com/rafkind/paintown/animator/events/scala/events.scala b/editor/src/main/scala/com/rafkind/paintown/animator/events/scala/events.scala new file mode 100644 index 000000000..f387eaade --- /dev/null +++ b/editor/src/main/scala/com/rafkind/paintown/animator/events/scala/events.scala @@ -0,0 +1,1131 @@ +package com.rafkind.paintown.animator.events.scala + +// import java.util._; +import java.awt.Graphics +import java.awt.Graphics2D +import java.awt.Color +import java.awt.Dimension +import java.awt.GridBagConstraints +import java.awt.event._; +import java.awt.image._ +import javax.swing._; +import javax.swing.event._; +import com.rafkind.paintown.animator.Animation; +import com.rafkind.paintown.animator.DrawArea; +import com.rafkind.paintown.animator.BoundingBox; +import com.rafkind.paintown.Token; +import com.rafkind.paintown.Undo; +import com.rafkind.paintown.Lambda0; +import com.rafkind.paintown.animator.events.AnimationEvent; +import com.rafkind.paintown.animator.NewAnimator +import org.swixml.SwingEngine; + +import java.io.File +import com.rafkind.paintown.Data; +import com.rafkind.paintown.MaskedImage; + +object Utils{ + def toScalaList[T](list:java.util.List[T]):List[T] = { + var out:List[T] = List[T]() + for (item <- scala.collection.JavaConversions.asScalaBuffer(list)){ + out = out :+ item + } + out + } +} + +abstract class AnimationEventNotifier { + var listeners:List[Lambda0] = List() + def addUpdateListener(update:Lambda0){ + for (listener <- listeners){ + if (listener == update){ + return + } + } + listeners = listeners :+ update; + } + + def updateListeners(){ + for (listener <- listeners){ + listener.invoke() + } + } +} + +class Attack(){ + val DEFAULT_FORCE_X = 1.7 + val DEFAULT_FORCE_Y = 4.4 + + var x1:Int = 0 + var y1:Int = 0 + var x2:Int = 0 + var y2:Int = 0 + var damage:Int = 0 + var forceX:Double = DEFAULT_FORCE_X + var forceY:Double = DEFAULT_FORCE_Y + + def copy() = { + val attack = new Attack() + attack.x1 = x1 + attack.y1 = y1 + attack.x2 = x2 + attack.y2 = y2 + attack.damage = damage + attack.forceX = forceX + attack.forceY = forceY + attack + } + + def isEmpty():Boolean = { + x1 == 0 && y1 == 0 && x2 == 0 && y2 == 0 + } +} + +// class AttackEvent extends AnimationEvent with AnimationEventNotifer { +class AttackEvent extends AnimationEventNotifier with AnimationEvent { + var onDestroy = () => { } + var attacks:List[Attack] = List[Attack]() + + private def addAttack(attack:Attack) = { + attacks = attacks :+ attack + } + + override def copy() = { + val event = new AttackEvent() + event.attacks = attacks.map((attack:Attack) => { attack.copy() }) + event + } + + override def destroy() = { + onDestroy() + } + + def getDescription() = "Creates an attack box that is used to discover collissions between characters. x1, y1 specify the upper left hand corner of the box while x2, y2 specify the lower right hand corner. 'damage' specifies the amount of life this attack will take away and 'force' specifies the velocity that is given to the character being hit." + + def parseAttacks(token:Token):List[Attack] = { + (parse(token) :: Utils.toScalaList(token.findTokens("box")).map(parse)).filter(x => ! x.isEmpty()) + } + + override def loadToken(token:Token){ + this.attacks = parseAttacks(token) + } + + def parse(token:Token):Attack = { + val attack = new Attack(); + + val x1_token = token.findToken("x1"); + if (x1_token != null){ + attack.x1 = x1_token.readInt(0); + } + + val y1_token = token.findToken("y1"); + if (y1_token != null){ + attack.y1 = y1_token.readInt(0); + } + + val x2_token = token.findToken("x2"); + if (x2_token != null){ + attack.x2 = x2_token.readInt(0); + } + + val y2_token = token.findToken("y2"); + if (y2_token != null){ + attack.y2 = y2_token.readInt(0); + } + + val damage_token = token.findToken("damage"); + if (damage_token != null){ + attack.damage = damage_token.readInt(0); + } + + val force_token = token.findToken("force"); + if (force_token != null){ + /* + attack.force = force_token.readInt(0); + */ + try{ + val x = force_token.readDouble(0); + val y = force_token.readDouble(1); + attack.forceX = x; + attack.forceY = y; + } catch { + case fail:NoSuchElementException => { + } + } + } + + attack + } + + override def getToken():Token = { + val temp = new Token(); + temp.addToken(new Token("attack")); + for (attack <- attacks){ + if (attack.isEmpty()){ + // temp.addToken(new Token("box")); + } else { + val box = new Token(); + temp.addToken(box); + box.addToken(new Token("box")); + box.addToken(("x1" :: attack.x1.toString() :: List[String]()).toArray) + box.addToken(("y1" :: attack.y1.toString() :: List[String]()).toArray) + box.addToken(("x2" :: attack.x2.toString() :: List[String]()).toArray) + box.addToken(("y2" :: attack.y2.toString() :: List[String]()).toArray) + box.addToken(("force" :: attack.forceX.toString() :: attack.forceY.toString() :: List[String]()).toArray) + box.addToken(("damage" :: attack.damage.toString() :: List[String]()).toArray) + } + } + + temp + } + + override def getName():String = { + getToken().toString() + } + + override def getEditor(animation:Animation, area:DrawArea):JPanel = { + if (attacks.isEmpty){ + attacks = List[Attack](new Attack()) + getEditor(animation, area, attacks.head) + } else { + getEditor(animation, area, attacks.head) + } + } + + override def interact(animation:Animation){ + if (attacks.isEmpty){ + animation.setAttack(new BoundingBox(0, 0, 0, 0)); + } else { + val attack = attacks.head + animation.setAttack(new BoundingBox(attack.x1, attack.y1, attack.x2, attack.y2)) + } + } + + def getEditor(animation:Animation, area:DrawArea, attack:Attack):JPanel = { + val engine = new SwingEngine("animator/eventattack.xml") + // ((JPanel)engine.getRootComponent()).setSize(200,150); + + val x1spin = engine.find("x1").asInstanceOf[JSpinner] + x1spin.setValue(new Integer(attack.x1)); + x1spin.addChangeListener(new ChangeListener(){ + def stateChanged(changeEvent:ChangeEvent){ + attack.x1 = x1spin.getValue().asInstanceOf[Integer].intValue() + interact(animation); + updateListeners() + animation.forceRedraw(); + } + }); + + val y1spin = engine.find( "y1" ).asInstanceOf[JSpinner]; + y1spin.setValue(new Integer(attack.y1)); + y1spin.addChangeListener(new ChangeListener() { + def stateChanged(changeEvent:ChangeEvent){ + attack.y1 = y1spin.getValue().asInstanceOf[Integer].intValue(); + interact(animation); + updateListeners() + animation.forceRedraw(); + } + }); + + val x2spin = engine.find("x2").asInstanceOf[JSpinner]; + x2spin.setValue(new Integer(attack.x2)); + x2spin.addChangeListener(new ChangeListener(){ + def stateChanged(changeEvent:ChangeEvent){ + attack.x2 = x2spin.getValue().asInstanceOf[Integer].intValue(); + interact(animation); + updateListeners() + animation.forceRedraw(); + } + }); + + val y2spin = engine.find("y2").asInstanceOf[JSpinner]; + y2spin.setValue(new Integer(attack.y2)); + y2spin.addChangeListener(new ChangeListener(){ + def stateChanged(changeEvent:ChangeEvent){ + attack.y2 = y2spin.getValue().asInstanceOf[Integer].intValue(); + interact(animation); + updateListeners() + animation.forceRedraw(); + } + }); + + val forcespinX = engine.find("forceX").asInstanceOf[JSpinner]; + forcespinX.setModel(new SpinnerNumberModel(attack.forceX, 0, 1000, 0.1)); + forcespinX.addChangeListener(new ChangeListener(){ + def stateChanged(changeEvent:ChangeEvent){ + attack.forceX = forcespinX.getValue().asInstanceOf[Double].doubleValue(); + updateListeners() + } + }); + + val forcespinY = engine.find("forceY").asInstanceOf[JSpinner]; + forcespinY.setModel(new SpinnerNumberModel(attack.forceY, 0, 1000, 0.1)); + forcespinY.addChangeListener(new ChangeListener(){ + def stateChanged(changeEvent:ChangeEvent){ + attack.forceY = forcespinY.getValue().asInstanceOf[Double].doubleValue(); + updateListeners() + } + }); + + val damagespin = engine.find("damage").asInstanceOf[JSpinner]; + damagespin.setValue(new Integer(attack.damage)); + damagespin.addChangeListener(new ChangeListener(){ + def stateChanged(changeEvent:ChangeEvent){ + attack.damage = damagespin.getValue().asInstanceOf[Integer].intValue(); + updateListeners() + } + }); + + val toggle = engine.find("toggle").asInstanceOf[JButton]; + toggle.addActionListener(new AbstractAction(){ + var toggled:Boolean = false; + + def calculateX(x:Integer) = (x / area.getScale() - area.getCenterX() - (animation.getX() + animation.getOffsetX() - animation.getWidth() / 2)).toInt + def calculateY(y:Integer) = (y / area.getScale() - area.getCenterY() - (animation.getY() + animation.getOffsetY() - animation.getHeight())).toInt + + val listener = new MouseInputAdapter(){ + def leftClick(e:MouseEvent):Boolean = e.getButton() == MouseEvent.BUTTON1 + def rightClick(e:MouseEvent):Boolean = e.getButton() == MouseEvent.BUTTON2 + var movingLeft = false + + var oldX1 = 0 + var oldY1 = 0 + var oldX2 = 0 + var oldY2 = 0 + + override def mousePressed(e:MouseEvent){ + val oldX1 = attack.x1 + val oldY1 = attack.y1 + val oldX2 = attack.x2 + val oldY2 = attack.y2 + Undo.addUndo("Set attack box", () => { + attack.x1 = oldX1 + attack.x2 = oldX2 + attack.y1 = oldY1 + attack.y2 = oldY2 + x1spin.setValue(new Integer(attack.x1)) + x2spin.setValue(new Integer(attack.x2)) + y1spin.setValue(new Integer(attack.y1)) + y2spin.setValue(new Integer(attack.y2)) + }) + + if (leftClick(e)){ + attack.x1 = calculateX(e.getX()) + attack.y1 = calculateY(e.getY()) + x1spin.setValue(new Integer(attack.x1)); + y1spin.setValue(new Integer(attack.y1)); + movingLeft = true + } else { + attack.x2 = calculateX(e.getX()) + attack.y2 = calculateY(e.getY()) + x2spin.setValue(new Integer(attack.x2)); + y2spin.setValue(new Integer(attack.y2)); + movingLeft = false + } + + updateListeners() + interact(animation); + animation.forceRedraw(); + } + + override def mouseDragged(e:MouseEvent){ + if (movingLeft){ + attack.x1 = calculateX(e.getX()) + attack.y1 = calculateY(e.getY()) + x1spin.setValue(new Integer(attack.x1)); + y1spin.setValue(new Integer(attack.y1)); + } else { + attack.x2 = calculateX(e.getX()) + attack.y2 = calculateY(e.getY()) + x2spin.setValue(new Integer(attack.x2)); + y2spin.setValue(new Integer(attack.y2)); + } + + updateListeners() + interact(animation); + animation.forceRedraw(); + } + }; + + def actionPerformed(event:ActionEvent){ + if (toggled){ + toggle.setText("Draw attack box"); + area.enableMovement(); + area.removeMouseListener(listener); + area.removeMouseMotionListener(listener); + area.removeHelpText() + onDestroy = () => {} + } else { + toggle.setText("Stop drawing"); + area.disableMovement(); + area.addMouseListener(listener); + area.addMouseMotionListener(listener); + area.addHelpText("Left click to change X1/Y1", "Right click to change X2/Y2") + onDestroy = () => { + area.removeMouseListener(listener); + area.removeMouseMotionListener(listener); + area.enableMovement(); + } + } + + toggled = ! toggled; + } + }); + + engine.getRootComponent().asInstanceOf[JPanel]; + } +} + +case class EffectPoint(var name:String, var x:Int, var y:Int) + +/* Names an arbitrary point relative to the character. */ +class EffectEvent extends AnimationEventNotifier with AnimationEvent { + var point = EffectPoint("", 0, 0) + var onDestroy = () => null + + def loadToken(token:Token){ + point.name = token.findToken("name").readString(0) + point.x = token.findToken("x").readInt(0) + point.y = token.findToken("y").readInt(0) + } + + override def copy() = { + val event = new EffectEvent() + event.point.name = point.name + event.point.x = point.x + event.point.y = point.y + event + } + + def getDescription() = "Gives a name to an arbitrary point" + + def getName():String = { + getToken().toString() + } + + def interact(animation:Animation){ + animation.addEffectPoint(point) + } + + def getEditor(animation:Animation, area:DrawArea):JPanel = { + val engine = new SwingEngine("animator/event-effect.xml"); + + val toggle = engine.find("point").asInstanceOf[JButton] + val nameText = engine.find("name").asInstanceOf[JTextField] + val xspin = engine.find("x").asInstanceOf[JSpinner] + val yspin = engine.find("y").asInstanceOf[JSpinner] + + nameText.setText(point.name) + def updateName(name:String){ + point.name = name + updateListeners() + animation.forceRedraw(); + } + + nameText.addActionListener(new AbstractAction(){ + def actionPerformed(event:ActionEvent){ + updateName(nameText.getText()) + } + }) + + nameText.getDocument().addDocumentListener(new DocumentListener(){ + def changedUpdate(e:DocumentEvent){ + updateName(nameText.getText()) + } + + def insertUpdate(e:DocumentEvent){ + updateName(nameText.getText()) + } + + def removeUpdate(e:DocumentEvent){ + updateName(nameText.getText()) + } + }) + + xspin.setValue(new Integer(point.x)) + xspin.addChangeListener(new ChangeListener(){ + def stateChanged(changeEvent:ChangeEvent){ + point.x = xspin.getValue().asInstanceOf[Integer].intValue() + updateListeners() + animation.forceRedraw() + } + }) + + yspin.setValue(new Integer(point.y)) + yspin.addChangeListener(new ChangeListener(){ + def stateChanged(changeEvent:ChangeEvent){ + point.y = yspin.getValue().asInstanceOf[Integer].intValue() + updateListeners() + animation.forceRedraw() + } + }) + + toggle.addActionListener(new AbstractAction(){ + var toggled = false + + def calculateX(x:Integer) = (x / area.getScale() - area.getCenterX() - (animation.getX() + animation.getOffsetX())).toInt + def calculateY(y:Integer) = (y / area.getScale() - area.getCenterY() - (animation.getY() + animation.getOffsetY())).toInt + + val listener = new MouseInputAdapter(){ + override def mousePressed(e:MouseEvent){ + point.x = calculateX(e.getX()) + point.y = calculateY(e.getY()) + xspin.setValue(new Integer(point.x)); + yspin.setValue(new Integer(point.y)); + updateListeners() + animation.forceRedraw(); + } + + override def mouseDragged(e:MouseEvent){ + point.x = calculateX(e.getX()) + point.y = calculateY(e.getY()) + xspin.setValue(new Integer(point.x)); + yspin.setValue(new Integer(point.y)); + updateListeners() + animation.forceRedraw(); + } + }; + + def actionPerformed(event:ActionEvent){ + if (toggled){ + toggle.setText("Set point") + area.enableMovement() + area.removeMouseListener(listener) + area.removeMouseMotionListener(listener) + onDestroy = () => null + } else { + toggle.setText("Setting point") + area.disableMovement() + area.addMouseListener(listener) + area.addMouseMotionListener(listener) + onDestroy = () => { + area.removeMouseListener(listener) + area.removeMouseMotionListener(listener) + area.enableMovement() + null + } + } + toggled = ! toggled + } + }) + + engine.getRootComponent().asInstanceOf[JPanel] + } + + def getToken():Token = { + val temp = new Token("effect"); + temp.addToken(new Token("effect")) + val nameToken = new Token("name") + nameToken.addToken(new Token("name")) + temp.addToken(nameToken) + nameToken.addToken(new Token(point.name)) + + val xToken = new Token("x") + xToken.addToken(new Token("x")) + temp.addToken(xToken) + xToken.addToken(new Token(point.x.toString())) + + val yToken = new Token("y") + yToken.addToken(new Token("y")) + temp.addToken(yToken) + yToken.addToken(new Token(point.y.toString())) + + temp + } + + def destroy(){ + onDestroy() + } + +} + +class FrameEvent extends AnimationEventNotifier with AnimationEvent { + var frame:String = "" + + def loadToken(token:Token){ + frame = token.readString(0) + } + + override def copy() = { + val event = new FrameEvent() + event.frame = frame + event + } + + def interact(animation:Animation){ + val path = Data.getDataPath() + "/" + animation.getBaseDirectory() + "/" + frame + try{ + /* + animation.setImage( MaskedImage.load( path ) ); + */ + if (animation.getMap() != null){ + animation.setImage(MaskedImage.load(path, animation.getMap())); + } else { + animation.setImage(MaskedImage.load(path)); + } + animation.delay(); + } catch { + case e:Exception => { + e.printStackTrace(); + System.out.println("Could not load " + path); + } + } + } + + def getDescription() = "Sets the current image to be shown. The frame event will wait for the 'delay' amount of time before moving on to the next event." + + def getName():String = { + getToken().toString() + } + + def getEditor(animation:Animation, area2:DrawArea):JPanel = { + val engine = new SwingEngine("animator/eventframe.xml"); + // ((JPanel)engine.getRootComponent()).setSize(350,270); + // JPanel canvas = (JPanel)engine.find("canvas"); + val canvas = engine.getRootComponent().asInstanceOf[JPanel] + + class drawArea extends JComponent { + var img:BufferedImage = null; + override def paint(g:Graphics){ + g.setColor(new Color(0, 0, 0)); + // g.fillRect( 0, 0, 640, 480 ); + g.fillRect(1, 1, getWidth() - 1, getHeight() - 1); + if (img != null){ + // g.drawImage( img, 125 - (img.getTileWidth()/2), 100 - (img.getTileHeight()/2), null ); + val g2d = g.asInstanceOf[Graphics2D]; + val scale = Math.min((getWidth() - 5.0) / img.getTileWidth(), (getHeight() - 5.0) / img.getTileHeight()); + g2d.scale(scale, scale); + // g.drawImage(img, (int)(getWidth() / 2 - (img.getTileWidth()*scale/2)), (int)(getHeight() / 2 - img.getTileHeight()*scale/2), null); + // g.drawImage(img, (int)(getWidth() / 2 - (img.getTileWidth()*scale/2)), (int)(getHeight() / 2 - img.getTileHeight()*scale/2), null); + g.drawImage(img, (getWidth() / 2 - (img.getWidth(null)*scale/2)).toInt, (getHeight() / 2 - img.getHeight(null)*scale/2).toInt, null); + // g.drawImage(img, (int) ((getWidth() / 2 - (img.getTileWidth()/2)) * scale), (int)((getHeight() / 2 - (img.getTileHeight()/2)) * scale), null); + } + } + + def setImage(i:BufferedImage){ + img = i; + } + }; + + val area = new drawArea(); + + /* + area.setSize(350,200); + area.setPreferredSize( new Dimension( 350,200 ) ); + */ + area.setPreferredSize(new Dimension(100,100)); + + val constraints = new GridBagConstraints(); + constraints.gridx = 0; + constraints.gridy = 0; + constraints.weightx = 1; + constraints.weighty = 1; + constraints.fill = GridBagConstraints.BOTH; + constraints.anchor = GridBagConstraints.NORTHWEST; + canvas.add(area, constraints); + + // canvas.add(area); + + val framebox = engine.find("frame").asInstanceOf[JComboBox[String]] + var index = 0; + var count = -1; + for (name <- NewAnimator.getFiles(animation.getBaseDirectory())){ + count += 1 + framebox.addItem(name); + if (name.endsWith(frame)){ + index = count; + } + } + + framebox.addActionListener(new ActionListener(){ + override def actionPerformed(event:ActionEvent){ + frame = framebox.getSelectedItem().asInstanceOf[String] + try{ + area.setImage(MaskedImage.load(Data.getDataPath() + "/" + animation.getBaseDirectory() + "/" + frame)); + area.repaint(); + updateListeners() + } catch { + case e:Exception => { + System.out.println("Couldn't load file: " + frame); + e.printStackTrace(); + } + } + } + }); + + framebox.setSelectedIndex(index); + + engine.getRootComponent().asInstanceOf[JPanel] + } + + def setFrame(frame:String){ + this.frame = frame + } + + def getToken():Token = { + val temp = new Token("frame"); + temp.addToken(new Token("frame")); + temp.addToken(new Token(frame)); + temp; + } + + def destroy(){ + } +} + +class MoveEvent extends AnimationEventNotifier with AnimationEvent { + var x:Int = 0 + var y:Int = 0 + var z:Int = 0 + + override def copy() = { + val event = new MoveEvent() + event.x = x + event.y = y + event.z = z + event + } + + def loadToken(token:Token){ + x = token.readInt(0) + y = token.readInt(1) + z = token.readInt(2) + } + + def interact(animation:Animation){ + animation.moveX(x); + animation.moveY(y); + } + + def getName():String = { + getToken().toString(); + } + + def getEditor(animation:Animation, area:DrawArea):JPanel = { + val engine = new SwingEngine("animator/eventmove.xml") + engine.getRootComponent().asInstanceOf[JPanel].setSize(200,150) + + val xspin = engine.find("x").asInstanceOf[JSpinner]; + xspin.setValue(new Integer(x)); + xspin.addChangeListener(new ChangeListener(){ + def stateChanged(changeEvent:ChangeEvent){ + x = xspin.getValue().asInstanceOf[Integer].intValue() + updateListeners() + } + }); + val yspin = engine.find("y").asInstanceOf[JSpinner]; + yspin.setValue(new Integer(y)); + yspin.addChangeListener(new ChangeListener(){ + def stateChanged(changeEvent:ChangeEvent){ + y = yspin.getValue().asInstanceOf[Integer].intValue(); + updateListeners() + } + }); + val zspin = engine.find("z").asInstanceOf[JSpinner]; + zspin.setValue(new Integer(z)); + zspin.addChangeListener(new ChangeListener(){ + def stateChanged(changeEvent:ChangeEvent){ + z = zspin.getValue().asInstanceOf[Integer].intValue() + updateListeners() + } + }); + + engine.getRootComponent().asInstanceOf[JPanel]; + } + + override def getToken():Token = { + val temp = new Token("move") + temp.addToken(new Token("move")) + temp.addToken(new Token(x.toString())) + temp.addToken(new Token(y.toString())) + temp.addToken(new Token(z.toString())) + temp + } + + override def destroy(){ + } + + def getDescription() = "Moves the character by the given x, y, and z coordinates" +} + +class HittableEvent extends AnimationEventNotifier with AnimationEvent { + var hit:Boolean = true + + override def copy() = { + val event = new HittableEvent() + event.hit = hit + event + } + + def destroy(){ + } + + def getName():String = getToken().toString() + + def getToken():Token = { + val temp = new Token() + temp.addToken(new Token("hittable")); + temp.addToken(new Token(hit.toString())) + temp + } + + def loadToken(token:Token){ + hit = token.readBoolean(0) + } + + def getDescription() = "Makes the character able to be attacked or not" + + def interact(animation:Animation){ + } + + def getEditor(animation:Animation, area:DrawArea):JPanel = { + val engine = new SwingEngine("animator/event-hittable.xml"); + val change = engine.find("hit").asInstanceOf[JCheckBox] + change.setSelected(hit) + change.addChangeListener(new ChangeListener(){ + def stateChanged(changeEvent:ChangeEvent){ + hit = change.isSelected() + updateListeners() + } + }); + + engine.getRootComponent().asInstanceOf[JPanel]; + } +} + +case class Defense(var x1:Int, var y1:Int, var x2:Int, var y2:Int){ +} + +class DefenseEvent extends AnimationEventNotifier with AnimationEvent { + + var boxes:List[Defense] = List[Defense]() + var onDestroy = () => null + + override def copy() = { + val event = new DefenseEvent() + event.boxes = boxes.map(box => box match { + case Defense(x1, y1, x2, y2) => Defense(x1, y1, x2, y2) + }) + event + } + + def destroy(){ + onDestroy() + } + + override def getToken():Token = { + val temp = new Token() + temp.addToken(new Token("defense")); + for (defense <- boxes.filter(!isEmpty(_))){ + val box = new Token(); + temp.addToken(box); + box.addToken(new Token("box")); + box.addToken(("x1" :: defense.x1.toString() :: List[String]()).toArray) + box.addToken(("y1" :: defense.y1.toString() :: List[String]()).toArray) + box.addToken(("x2" :: defense.x2.toString() :: List[String]()).toArray) + box.addToken(("y2" :: defense.y2.toString() :: List[String]()).toArray) + } + temp + } + + override def getName():String = getToken().toString() + + def parse(token:Token):Defense = { + val x1_token = token.findToken("x1"); + var x1 = 0 + var y1 = 0 + var x2 = 0 + var y2 = 0 + if (x1_token != null){ + x1 = x1_token.readInt(0); + } + + val y1_token = token.findToken("y1"); + if (y1_token != null){ + y1 = y1_token.readInt(0); + } + + val x2_token = token.findToken("x2"); + if (x2_token != null){ + x2 = x2_token.readInt(0); + } + + val y2_token = token.findToken("y2"); + if (y2_token != null){ + y2 = y2_token.readInt(0); + } + + Defense(x1, y1, x2, y2) + } + + def isEmpty(defense:Defense):Boolean = { + defense match { + case Defense(0, 0, 0, 0) => true + case _ => false + } + } + + def loadToken(token:Token){ + this.boxes = (parse(token) :: Utils.toScalaList(token.findTokens("box")).map(parse)).filter(!isEmpty(_)) + } + + def getDescription() = "Not used yet" + + def interact(animation:Animation){ + if (boxes.isEmpty){ + animation.setDefense(new BoundingBox(0, 0, 0, 0)); + } else { + boxes.head match { + case Defense(x1, y1, x2, y2) => animation.setDefense(new BoundingBox(x1, y1, x2, y2)) + } + } + } + + def getEditor(animation:Animation, area:DrawArea):JPanel = { + if (boxes.isEmpty){ + boxes = boxes :+ new Defense(0, 0, 0, 0) + } + + getEditor(animation, area, boxes.head) + } + + def getEditor(animation:Animation, area:DrawArea, defense:Defense):JPanel = { + val engine = new SwingEngine("animator/event-defense.xml"); + // ((JPanel)engine.getRootComponent()).setSize(200,150); + + val x1spin = engine.find("x1").asInstanceOf[JSpinner] + x1spin.setValue(new Integer(defense.x1)); + x1spin.addChangeListener(new ChangeListener(){ + def stateChanged(changeEvent:ChangeEvent){ + defense.x1 = x1spin.getValue().asInstanceOf[Integer].intValue() + interact(animation); + animation.forceRedraw(); + updateListeners() + } + }); + + val y1spin = engine.find("y1").asInstanceOf[JSpinner] + y1spin.setValue(new Integer(defense.y1)); + y1spin.addChangeListener( new ChangeListener() { + def stateChanged(changeEvent:ChangeEvent){ + defense.y1 = y1spin.getValue().asInstanceOf[Integer].intValue() + interact(animation); + animation.forceRedraw(); + updateListeners() + } + }); + + val x2spin = engine.find("x2").asInstanceOf[JSpinner] + x2spin.setValue(new Integer(defense.x2)); + x2spin.addChangeListener(new ChangeListener(){ + def stateChanged(changeEvent:ChangeEvent){ + defense.x2 = x2spin.getValue().asInstanceOf[Integer].intValue() + interact(animation); + animation.forceRedraw(); + updateListeners() + } + }); + + val y2spin = engine.find("y2").asInstanceOf[JSpinner] + y2spin.setValue(new Integer(defense.y2)); + y2spin.addChangeListener(new ChangeListener(){ + def stateChanged(changeEvent:ChangeEvent){ + defense.y2 = y2spin.getValue().asInstanceOf[Integer].intValue() + interact(animation); + animation.forceRedraw(); + updateListeners() + } + }); + + val toggle = engine.find("toggle").asInstanceOf[JButton] + toggle.addActionListener(new AbstractAction(){ + var toggled = false + + def calculateX(x:Integer) = (x / area.getScale() - area.getCenterX() - (animation.getX() + animation.getOffsetX() - animation.getWidth() / 2)).toInt + def calculateY(y:Integer) = (y / area.getScale() - area.getCenterY() - (animation.getY() + animation.getOffsetY() - animation.getHeight())).toInt + + val listener = new MouseInputAdapter(){ + override def mousePressed(e:MouseEvent){ + defense.x1 = calculateX(e.getX()) + defense.y1 = calculateY(e.getY()) + x1spin.setValue(new Integer(defense.x1)); + y1spin.setValue(new Integer(defense.y1)); + interact(animation); + updateListeners() + animation.forceRedraw(); + } + + override def mouseDragged(e:MouseEvent){ + defense.x2 = calculateX(e.getX()) + defense.y2 = calculateY(e.getY()) + x2spin.setValue(new Integer(defense.x2)); + y2spin.setValue(new Integer(defense.y2)); + interact(animation); + updateListeners() + animation.forceRedraw(); + } + }; + + def actionPerformed(event:ActionEvent){ + if (toggled){ + toggle.setText("Draw defense box") + area.enableMovement() + area.removeMouseListener(listener) + area.removeMouseMotionListener(listener) + onDestroy = () => null + } else { + toggle.setText("Stop drawing") + area.disableMovement() + area.addMouseListener(listener) + area.addMouseMotionListener(listener) + onDestroy = () => { + area.removeMouseListener(listener) + area.removeMouseMotionListener(listener) + area.enableMovement() + null; + } + } + toggled = ! toggled + } + }); + + engine.getRootComponent().asInstanceOf[JPanel] + } +} + +class RelativeOffsetEvent extends AnimationEventNotifier with AnimationEvent { + var x:Int = 0 + var y:Int = 0 + + def loadToken(token:Token){ + x = token.readInt(0) + y = token.readInt(1) + } + + def copy():AnimationEvent = { + val event = new RelativeOffsetEvent() + event.x = x + event.y = y + event + } + + def interact(animation:Animation){ + animation.setOffsetX(x + animation.getOffsetX()) + animation.setOffsetY(y + animation.getOffsetY()) + } + + def setX(x:Int){ + this.x = x + } + + def getX():Int = x + + def setY(y:Int){ + this.y = y + } + + def getY():Int = y + + def getName():String = getToken().toString() + + def getEditor(animation:Animation, area:DrawArea):JPanel = { + val engine = new SwingEngine("animator/eventoffset.xml"); + engine.getRootComponent().asInstanceOf[JPanel].setSize(200,100); + + val self = this + + val xspin = engine.find("x").asInstanceOf[JSpinner]; + xspin.setValue(new Integer(self.x)); + xspin.addChangeListener(new ChangeListener(){ + def stateChanged(changeEvent:ChangeEvent){ + self.x = xspin.getValue().asInstanceOf[Integer].intValue() + updateListeners(); + /* reset the animation to the current event so the + * current offset is correct + */ + animation.reset() + interact(animation) + animation.forceRedraw() + } + }); + + val yspin = engine.find("y").asInstanceOf[JSpinner]; + yspin.setValue(new Integer(self.y)); + yspin.addChangeListener(new ChangeListener(){ + def stateChanged(changeEvent:ChangeEvent){ + self.y = yspin.getValue().asInstanceOf[Integer].intValue() + updateListeners() + animation.reset() + interact(animation); + animation.forceRedraw() + } + }); + + return engine.getRootComponent().asInstanceOf[JPanel]; + } + + def getToken():Token = { + val temp = new Token() + temp.addToken(new Token("relative-offset")) + temp.addToken(new Token(x.toString())) + temp.addToken(new Token(y.toString())) + + temp + } + + def destroy(){ + } + + def getDescription():String = "Moves the location of the sprite by the given x and y amounts. Use offset to make sure sprites line up within an animation." +} + +class PerpetualEvent extends AnimationEventNotifier with AnimationEvent { + var enabled:Boolean = false + + def loadToken(token:Token){ + enabled = token.readBoolean(0) + } + + def copy():AnimationEvent = { + val event = new PerpetualEvent() + event.enabled = enabled + event + } + + def interact(animation:Animation){ + } + + def getName():String = getToken().toString() + + def getEditor(animation:Animation, area:DrawArea):JPanel = { + val engine = new SwingEngine("animator/event-perpetual.xml"); + engine.getRootComponent().asInstanceOf[JPanel].setSize(200,100); + + val self = this + + val choose = engine.find("choose").asInstanceOf[JComboBox[Boolean]] + choose.addItem(true) + choose.addItem(false) + choose.setSelectedItem(enabled) + + choose.addActionListener(new ActionListener(){ + override def actionPerformed(event:ActionEvent){ + self.enabled = choose.getSelectedItem().asInstanceOf[Boolean] + } + }); + + engine.getRootComponent().asInstanceOf[JPanel] + } + + def getToken():Token = { + val temp = new Token() + temp.addToken(new Token("perpetual")) + temp.addToken(new Token(enabled.toString())) + temp + } + + def destroy(){ + } + + def getDescription():String = "Allows projectiles to not die immediately when they collide with an enemy." +} diff --git a/editor/src/main/scala/com/rafkind/paintown/animator/tools.scala b/editor/src/main/scala/com/rafkind/paintown/animator/tools.scala new file mode 100644 index 000000000..4ac40b558 --- /dev/null +++ b/editor/src/main/scala/com/rafkind/paintown/animator/tools.scala @@ -0,0 +1,208 @@ +package com.rafkind.paintown.animator + +import javax.swing._ +import java.awt.event._ +import java.awt.Color +import java.io.File +import javax.swing.event.ChangeListener +import javax.swing.event.ChangeEvent +import javax.swing.event.DocumentListener +import javax.swing.event.DocumentEvent +import com.rafkind.paintown.Undo +import com.rafkind.paintown.MaskedImage +import org.swixml.SwingEngine; + +object Tools{ + def makeBackgroundTool(character:AnimatedObject, area:DrawArea):JPanel = { + var panel = new JPanel() + val color = new JColorChooser(area.backgroundColor()); + color.setPreviewPanel(new JPanel()); + panel.add(color); + color.getSelectionModel().addChangeListener(new ChangeListener(){ + val self = this + def stateChanged(change:ChangeEvent){ + val old = character.getDrawProperties().getBackgroundColor() + character.getDrawProperties().setBackgroundColor(color.getSelectionModel().getSelectedColor()) + area.repaint() + + Undo.addUndo("Set color to " + old, () => { + color.getSelectionModel().removeChangeListener(self) + character.getDrawProperties().setBackgroundColor(old) + color.getSelectionModel().setSelectedColor(old) + area.repaint() + color.getSelectionModel().addChangeListener(self) + }) + } + }); + + character.getDrawProperties().addListener(new DrawProperties.Listener(){ + override def updateBackgroundColor(newColor:Color){ + color.setColor(newColor); + area.repaint(); + } + }); + + panel + } + + def makeGridTool(area:DrawArea):JPanel = { + val context = new SwingEngine("animator/animation-tools.xml") + val guide = context.find("guide").asInstanceOf[JSlider]; + guide.setValue(area.getGuideSize()); + guide.addChangeListener(new ChangeListener(){ + def stateChanged(change:ChangeEvent){ + area.setGuideSize(guide.getValue()); + area.repaint(); + } + }); + + context.find("grid").asInstanceOf[JPanel] + } + + def makeOverlayImageTool(parent:JPanel, area:DrawArea):JPanel = { + val context = new SwingEngine("animator/animation-tools.xml") + val enableButton = context.find("overlay:enable").asInstanceOf[JCheckBox] + var lastFile:String = "" + + def update(path:String){ + lastFile = path + if (enableButton.isSelected()){ + try{ + area.setOverlayImage(MaskedImage.load(path)) + } catch { + case e:Exception => { + area.setOverlayImage(null) + } + } + } else { + area.setOverlayImage(null) + } + area.repaint() + } + + enableButton.addActionListener(new AbstractAction(){ + def actionPerformed(event:ActionEvent){ + if (enableButton.isSelected()){ + enableButton.setText("Enabled") + } else { + enableButton.setText("Disabled") + } + + update(lastFile) + } + }) + + val filename = context.find("overlay:file").asInstanceOf[JTextField] + val choose = context.find("overlay:choose").asInstanceOf[JButton] + choose.addActionListener(new AbstractAction(){ + def actionPerformed(event:ActionEvent){ + val file = new JFileChooser() + val value = file.showOpenDialog(parent) + if (value == JFileChooser.APPROVE_OPTION){ + val selected = file.getSelectedFile() + filename.setText(selected.getPath()) + update(selected.getPath()) + } + } + }) + + filename.addActionListener(new AbstractAction(){ + def actionPerformed(event:ActionEvent){ + update(filename.getText()) + } + }) + + val rotationText = context.find("overlay:rotation-text").asInstanceOf[JLabel] + rotationText.setText("Rotation: 0") + + val rotation = context.find("overlay:rotation").asInstanceOf[JSlider] + rotation.setValue(0) + rotation.addChangeListener(new ChangeListener(){ + def stateChanged(change:ChangeEvent){ + rotationText.setText("Rotation: " + rotation.getValue().asInstanceOf[Int]) + area.setOverlayImageRotation(rotation.getValue().asInstanceOf[Int]) + area.repaint(); + } + }) + + val flipx = context.find("overlay:flip-x").asInstanceOf[JCheckBox] + val flipy = context.find("overlay:flip-y").asInstanceOf[JCheckBox] + + flipx.addActionListener(new AbstractAction(){ + def actionPerformed(event:ActionEvent){ + area.setOverlayImageFlipX(flipx.isSelected()); + area.repaint(); + } + }) + + flipy.addActionListener(new AbstractAction(){ + def actionPerformed(event:ActionEvent){ + area.setOverlayImageFlipY(flipy.isSelected()); + area.repaint(); + } + }) + + val relativeOffset = context.find("overlay:relative").asInstanceOf[JCheckBox] + relativeOffset.addActionListener(new AbstractAction(){ + def actionPerformed(event:ActionEvent){ + area.setOverlayRelativeOffset(relativeOffset.isSelected()) + area.repaint() + } + }) + + val offsetx = context.find("overlay:x").asInstanceOf[JSpinner] + val offsety = context.find("overlay:y").asInstanceOf[JSpinner] + + offsetx.setValue(new Integer(area.getOverlayImageOffsetX())) + offsetx.addChangeListener(new ChangeListener(){ + def stateChanged(event:ChangeEvent){ + area.setOverlayImageOffsetX(offsetx.getValue().asInstanceOf[Integer].intValue()) + area.repaint() + } + }) + + offsety.setValue(new Integer(area.getOverlayImageOffsetY())) + offsety.addChangeListener(new ChangeListener(){ + def stateChanged(event:ChangeEvent){ + area.setOverlayImageOffsetY(offsety.getValue().asInstanceOf[Integer].intValue()) + area.repaint() + } + }) + + val alphaText = context.find("overlay:alpha-text").asInstanceOf[JLabel] + val alpha = context.find("overlay:alpha").asInstanceOf[JSlider] + + alpha.setValue((area.getOverlayImageAlpha() * alpha.getMaximum()).toInt); + alphaText.setText("Transparency " + area.getOverlayImageAlpha()); + alpha.addChangeListener(new ChangeListener(){ + def stateChanged(change:ChangeEvent){ + area.setOverlayImageAlpha(alpha.getValue().asInstanceOf[Double].doubleValue() / + alpha.getMaximum().asInstanceOf[Double].doubleValue()) + alphaText.setText("Transparency " + area.getOverlayImageAlpha()) + area.repaint(); + } + }) + + val front = context.find("overlay:front").asInstanceOf[JRadioButton]; + val back = context.find("overlay:back").asInstanceOf[JRadioButton]; + front.setActionCommand("front"); + back.setActionCommand("back"); + + val change = new AbstractAction(){ + def actionPerformed(event:ActionEvent){ + if (event.getActionCommand().equals("front")){ + area.setOverlayImageFront(); + } else { + area.setOverlayImageBehind(); + } + area.repaint(); + } + } + + front.addActionListener(change); + back.addActionListener(change); + + context.find("overlay-image").asInstanceOf[JPanel] + } + +} diff --git a/editor/src/main/scala/com/rafkind/paintown/level/Editor2.scala b/editor/src/main/scala/com/rafkind/paintown/level/Editor2.scala new file mode 100644 index 000000000..8f2c8b835 --- /dev/null +++ b/editor/src/main/scala/com/rafkind/paintown/level/Editor2.scala @@ -0,0 +1,1653 @@ +package com.rafkind.paintown.level + +import java.io._ +import java.util.HashMap +import javax.swing._ +import java.awt._ +import java.awt.image._ +import java.awt.event._ +import javax.swing.event._ +import org.swixml.SwingEngine + +// import java.util.List +import scala.collection.immutable.List + +import com.rafkind.paintown.exception.LoadException +import com.rafkind.paintown.level.objects.Level +import com.rafkind.paintown.level.objects.Block +import com.rafkind.paintown.level.objects.Thing +import com.rafkind.paintown.level.objects.Character +import com.rafkind.paintown.level.objects.Item +import com.rafkind.paintown.Closer +import com.rafkind.paintown.Data +import com.rafkind.paintown.Lambda0 +import com.rafkind.paintown.Lambda1 +import com.rafkind.paintown.Lambda2 +import com.rafkind.paintown.Token +import com.rafkind.paintown.TokenReader +import com.rafkind.paintown.RelativeFileChooser +import javax.swing.filechooser.FileFilter + +class NewEditor extends JFrame("Paintown Editor"){ + var copy:Thing = null; + + construct(); + + def get[T](list:List[T], index:Int):T = { + list.find(list.indexOf(_) == index) match { + case Some(obj) => obj + case None => throw new Exception("failed to find " + index) + } + } + + def toScalaList[T](list:java.util.List[T]):List[T] = { + var out:List[T] = List[T]() + for (item <- scala.collection.JavaConversions.asScalaBuffer(list)){ + out = out :+ item + } + out + } + + def construct(){ + val screen = Toolkit.getDefaultToolkit().getScreenSize() + this.setSize((screen.getWidth() * 0.9).toInt, + (screen.getHeight() * 0.9).toInt); + + Closer.open(); + val menuBar = new JMenuBar(); + val menuProgram = new JMenu("Program"); + val quit = new JMenuItem( "Quit" ); + val data = new JMenuItem( "Data path" ); + val animationEditor = new JMenuItem("Run character animation editor"); + menuProgram.add(animationEditor); + menuProgram.add(data); + menuProgram.add(quit); + menuBar.add(menuProgram); + val menuLevel = new JMenu( "Level" ); + menuBar.add(menuLevel); + + val newLevel = new JMenuItem("New Level"); + menuLevel.add(newLevel); + val loadLevel = new JMenuItem("Open Level"); + menuLevel.add(loadLevel); + val saveLevel = new JMenuItem("Save Level"); + menuLevel.add(saveLevel); + val saveLevelAs = new JMenuItem("Save Level As"); + menuLevel.add(saveLevelAs); + val closeLevel = new JMenuItem("Close Level"); + menuLevel.add(closeLevel); + menuProgram.setMnemonic( KeyEvent.VK_P ); + data.setMnemonic( KeyEvent.VK_D ); + quit.setMnemonic( KeyEvent.VK_Q ); + quit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, Event.CTRL_MASK)); + newLevel.setMnemonic( KeyEvent.VK_N ); + newLevel.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, Event.CTRL_MASK)); + menuLevel.setMnemonic( KeyEvent.VK_L ); + saveLevel.setMnemonic( KeyEvent.VK_S ); + saveLevel.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, Event.CTRL_MASK)); + saveLevelAs.setMnemonic( KeyEvent.VK_A ); + saveLevelAs.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, Event.CTRL_MASK)); + loadLevel.setMnemonic( KeyEvent.VK_O ); + loadLevel.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, Event.CTRL_MASK)); + closeLevel.setMnemonic( KeyEvent.VK_W ); + closeLevel.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, Event.CTRL_MASK)); + + val tabbed = new JTabbedPane(); + this.getContentPane().add(tabbed); + + quit.addActionListener(new ActionListener(){ + override def actionPerformed(event:ActionEvent){ + Closer.close(); + } + }); + + animationEditor.addActionListener(new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + com.rafkind.paintown.animator.Animator2.main(new Array[String](0)); + } + }); + + data.addActionListener(new ActionListener(){ + override def actionPerformed(event:ActionEvent){ + /* just a container for an object */ + class ObjectBox { + var _internal:Object = null + + def internal = _internal + def internal_= (obj:Object):Unit = _internal = obj + } + + val engine = new SwingEngine("data-path.xml"); + val path = engine.find("path").asInstanceOf[JTextField]; + val box = new ObjectBox(); + box.internal = Data.getDataPath() + path.setText(Data.getDataPath().getPath()); + val change = engine.find("change").asInstanceOf[JButton]; + change.addActionListener(new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + val chooser = new JFileChooser(new File(".")); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + val returnVal = chooser.showOpenDialog(NewEditor.this); + if (returnVal == JFileChooser.APPROVE_OPTION){ + val newPath = chooser.getSelectedFile(); + path.setText(newPath.getPath()); + box.internal = newPath + } + } + }); + val save = engine.find("save").asInstanceOf[JButton]; + val cancel = engine.find("cancel").asInstanceOf[JButton]; + val dialog = new JDialog(NewEditor.this, "Paintown data path"); + save.addActionListener(new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + Data.setDataPath(box.internal.asInstanceOf[File]); + dialog.setVisible(false); + } + }); + cancel.addActionListener(new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + dialog.setVisible(false); + } + }); + val panel = engine.getRootComponent().asInstanceOf[JPanel]; + dialog.getContentPane().add(panel); + dialog.setSize(300, 300); + dialog.setVisible(true); + } + }); + + + val levels = new HashMap[Component, Level](); + + def doSave(level:Level, file:File){ + val out = new FileOutputStream(file); + new PrintStream(out).print(level.toToken().toString() + "\n"); + out.close(); + System.out.println(level.toToken().toString()); + } + + newLevel.addActionListener(new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + SwingUtilities.invokeLater(new Runnable(){ + def run(){ + val level = new Level(); + /* add 3 blocks to get the user started */ + for (i <- 1 to 3){ + level.getBlocks().asInstanceOf[java.util.List[Block]].add(new Block()); + } + + levels.put(tabbed.add(createEditPanel(level)), level); + } + }); + } + }); + + saveLevel.addActionListener(new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + if (tabbed.getSelectedComponent() != null){ + val level = levels.get(tabbed.getSelectedComponent()).asInstanceOf[Level]; + val file:File = level.getPath() match { + case null => userSelectFile("Save Level"); + case what => what + } + + /* write the text to a file */ + if (file != null){ + try{ + doSave(level, file); + level.setPath(file); + tabbed.setTitleAt(tabbed.getSelectedIndex(), file.getName()); + } catch { + case fail:Exception => { + fail.printStackTrace(); + showError("Could not save " + file + " because " + fail.getMessage()); + } + } + } + } + } + }); + + saveLevelAs.addActionListener(new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + if (tabbed.getSelectedComponent() != null){ + val level = levels.get(tabbed.getSelectedComponent()).asInstanceOf[Level]; + val file = userSelectFile("Save level as"); + /* write the text to a file */ + if (file != null){ + try{ + doSave(level, file); + level.setPath(file); + tabbed.setTitleAt(tabbed.getSelectedIndex(), file.getName()); + } catch { + case fail:Exception => { + fail.printStackTrace(); + showError("Could not save " + file + " because " + fail.getMessage() ); + } + } + } + } + } + }); + + closeLevel.addActionListener( new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + if ( tabbed.getSelectedComponent() != null ){ + levels.remove( tabbed.getSelectedComponent() ); + tabbed.remove( tabbed.getSelectedComponent() ); + } + } + }); + + loadLevel.addActionListener( new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + val chooser = new JFileChooser( new File( "." ) ); + chooser.setFileFilter(new FileFilter(){ + def accept(f:File):Boolean = { + f.isDirectory() || f.getName().endsWith( ".txt" ); + } + + def getDescription():String = { + "Level files"; + } + }); + + val returnVal = chooser.showOpenDialog( NewEditor.this ); + if ( returnVal == JFileChooser.APPROVE_OPTION ){ + val f = chooser.getSelectedFile(); + try{ + val level = new Level(f); + levels.put(tabbed.add(f.getName(), createEditPanel(level)), level); + } catch { + case fail:LoadException => { + showError( "Could not load " + f.getName() ); + System.out.println( "Could not load " + f.getName() ); + fail.printStackTrace(); + } + } + } + } + }); + + this.setJMenuBar(menuBar); + this.addWindowListener(new WindowAdapter{ + override def windowClosing(e:WindowEvent){ + Closer.close(); + } + }); + } + + def createEditPanel(level:Level):JComponent = { + val engine = new SwingEngine("main.xml"); + + val viewContainer = engine.find( "view" ).asInstanceOf[JPanel]; + val viewScroll = new JScrollPane(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); + + val view = new JPanel(){ + override def getPreferredSize():Dimension = { + level.getSize() + } + + override def paintComponent(g:Graphics){ + val h = viewScroll.getHorizontalScrollBar(); + val v = viewScroll.getVerticalScrollBar(); + g.setColor(new Color(64, 64, 64)); + g.fillRect(0, 0, level.getWidth().toInt, v.getVisibleAmount()); + g.clearRect(0, v.getVisibleAmount().toInt + 1, level.getWidth().toInt, level.getHeight().toInt); + level.render(g.asInstanceOf[Graphics2D], h.getValue(), 0, h.getVisibleAmount(), v.getVisibleAmount()); + } + }; + + viewScroll.setPreferredSize(new Dimension(200, 200)); + viewScroll.setViewportView(view); + + /* this allows smooth scrolling of the level */ + viewScroll.getViewport().setScrollMode(JViewport.BACKINGSTORE_SCROLL_MODE); + + /* + System.out.println( "JViewport.BLIT_SCROLL_MODE = " + JViewport.BLIT_SCROLL_MODE ); + System.out.println( "JViewport.BACKINGSTORE_SCROLL_MODE = " + JViewport.BACKINGSTORE_SCROLL_MODE ); + System.out.println( "JViewport.SIMPLE_SCROLL_MODE = " + JViewport.SIMPLE_SCROLL_MODE ); + System.out.println( "View scroll mode: " + viewScroll.getViewport().getScrollMode() ); + */ + viewScroll.getHorizontalScrollBar().setBackground(new Color(128, 255, 0)); + + def editSelected(thing:Thing){ + val dialog = new JDialog(NewEditor.this, "Edit"); + dialog.setSize(350, 350); + val editor = thing.getEditor(); + dialog.getContentPane().add(editor.createPane(level, new Lambda0(){ + override def invoke():Object = { + dialog.setVisible(false); + viewScroll.repaint(); + null + } + })); + dialog.setVisible(true); + } + + class ObjectListModel extends ListModel[File] { + var data:List[File] = defaultObjects(); + var listeners = List[ListDataListener](); + + def add(file:File){ + data = data :+ file + val event = new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, data.size, data.size); + for (listener <- listeners){ + listener.intervalAdded(event) + } + } + + def remove(index:Int){ + data = data.filterNot(data.indexOf(_) == index) + val event = new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index, index); + for (listener <- listeners){ + listener.intervalAdded(event); + } + } + + def getAll():List[File] = { + data + } + + override def addListDataListener(listener:ListDataListener){ + listeners = listeners :+ listener; + } + + override def getElementAt(index:Int) = { + this.data.find(data.indexOf(_) == index) match { + case Some(obj) => obj + case None => throw new Exception("failed to find " + index) + } + } + + override def getSize():Int = { + this.data.size; + } + + override def removeListDataListener(listener:ListDataListener){ + listeners = this.listeners diff List(listener) + } + }; + + val objectsModel = new ObjectListModel(); + + class Mouser extends MouseMotionAdapter with MouseInputListener { + var selected:Thing = null; + var dx:Double = 0 + var dy:Double = 0 + var sx:Double = 0 + var sy:Double = 0 + var currentPopup:JDialog = null + + def getSelected():Thing = { + selected; + } + + def setSelected(thing:Thing){ + selected = thing; + } + + override def mouseDragged(event:MouseEvent){ + if (selected != null){ + // System.out.println( "sx,sy: " + sx + ", " + sy + " ex,ey: " + (event.getX() / 2) + ", " + (event.getY() / 2) + " dx, dy: " + dx + ", " + dy ); + level.moveThing( selected, (sx + event.getX() / level.getScale() - dx).toInt, (sy + event.getY() / level.getScale() - dy).toInt); + viewScroll.repaint(); + } + } + + def leftClick(event:MouseEvent):Boolean = { + event.getButton() == MouseEvent.BUTTON1; + } + + def rightClick(event:MouseEvent):Boolean = { + event.getButton() == MouseEvent.BUTTON3; + } + + def selectThing(event:MouseEvent){ + val thing = findThingAt(event) + var has:Block = null; + for (block <- scala.collection.JavaConversions.asScalaBuffer(level.getBlocks().asInstanceOf[java.util.List[Block]])){ + block.setHighlight(false); + if (thing != null && block.hasThing(thing)){ + has = block; + } + } + + if (has != null){ + has.setHighlight(true); + viewScroll.repaint(); + } + + if (selected == null && thing != null){ + // selected = findThingAt( event ); + selected = thing; + selected.setSelected(true); + sx = selected.getX(); + sy = selected.getY() + level.getMinZ(); + // System.out.println( "Y: " + selected.getY() + " minZ: " + level.getMinZ() ); + dx = event.getX() / level.getScale(); + dy = event.getY() / level.getScale(); + // System.out.println( "Found: " + selected + " at " + event.getX() + " " + event.getY() ); + } + + if (getSelected() != null && event.getClickCount() == 2){ + try{ + editSelected(getSelected()); + } catch { + case fail:Exception => { + fail.printStackTrace(); + } + } + } + } + + def findFiles(dir:File, ending:String):List[File] = { + val all = dir.listFiles(new java.io.FileFilter(){ + override def accept(path:File):Boolean = { + path.isDirectory() || path.getName().endsWith(ending); + } + }); + var files = List[File]() + for (file <- all){ + if (file.isDirectory() ){ + files = files ++ findFiles(file, ending); + } else { + files = files :+ file + } + } + return files; + } + + def findBlock(event:MouseEvent):Block = { + val x = (event.getX() / level.getScale()).toInt + // System.out.println( event.getX() + " -> " + x ); + return level.findBlock(x); + /* + int total = 0; + for ( Iterator it = level.getBlocks().iterator(); it.hasNext(); ){ + Block b = (Block) it.next(); + if ( b.isEnabled() ){ + if ( x >= total && x <= total + b.getLength() ){ + return b; + } + total += b.getLength(); + } + } + return null; + */ + } + + def makeThing(head:Token, x:Int, y:Int, path:String):Thing = { + if (head.getName().equals("character")){ + val temp = new Token(); + temp.addToken(new Token("character")); + temp.addToken(("name" :: "TempName" :: List[String]()).toArray) + temp.addToken(("coords" :: x.toString :: y.toString :: List[String]()).toArray) + temp.addToken(("health" :: "40" :: List[String]()).toArray) + temp.addToken(("path" :: path :: List[String]()).toArray) + return new Character(temp); + } else if (head.getName().equals("item")){ + val temp = new Token(); + temp.addToken(new Token("item")); + temp.addToken(("coords" :: x.toString :: y.toString :: List[String]()).toArray) + temp.addToken(("path" :: path :: List[String]()).toArray) + // System.out.println( "Make item from " + temp.toString() ); + return new Item(temp); + } else if (head.getName().equals("cat")){ + val temp = new Token(); + temp.addToken(new Token("item")); + temp.addToken(("coords" :: x.toString :: y.toString :: List[String]()).toArray) + temp.addToken(("path" :: path :: List[String]()).toArray) + return new Item(temp); + } + throw new LoadException("Unknown type: " + head.getName()); + } + + def collectCharFiles():List[File] = { + return objectsModel.getAll() + } + + def showAddObject(block:Block){ + val breaks = new scala.util.control.Breaks + import breaks.{break, breakable} + + var x = -1; + breakable{ + for (check <- scala.collection.JavaConversions.asScalaBuffer(level.getBlocks().asInstanceOf[java.util.List[Block]])){ + if (check == block){ + break; + } + if (check.isEnabled()){ + x += check.getLength(); + } + } + } + showAddObjectPopup(new MouseEvent(NewEditor.this, -1, 0, 0, ((x + block.getLength() / 2) * level.getScale()).toInt, ((level.getMinZ() + level.getMaxZ()) * level.getScale() / 2).toInt, 1, false)); + } + + def showAddObjectPopup(event:MouseEvent){ + // JPanel panel = new JPanel(); + val files = collectCharFiles(); + val panel = Box.createVerticalBox(); + val all = new JList(new java.util.Vector(scala.collection.JavaConversions.asJavaList(files))); + panel.add(new JScrollPane(all)); + val add = new JButton("Add"); + val close = new JButton("Close"); + val buttons = Box.createHorizontalBox(); + buttons.add(add); + buttons.add(close); + panel.add(buttons); + if (currentPopup != null){ + currentPopup.setVisible(false); + } + val dialog = new JDialog(NewEditor.this, "Add"); + dialog.getContentPane().add( panel ); + dialog.setSize(220, 250); + dialog.setLocation(event.getX() - viewScroll.getHorizontalScrollBar().getValue(), event.getY()); + close.addActionListener(new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + dialog.setVisible(false); + } + }); + currentPopup = dialog; + dialog.setVisible(true); + + def addThing(file:File){ + def mid(a:Int, b:Int, c:Int):Int = { + Math.max(Math.min(b, c), a); + } + + try{ + val block = findBlock(event); + if (block != null){ + val reader = new TokenReader(dataPath(file)); + val head = reader.nextToken(); + var x = (event.getX() / level.getScale()).toInt; + val y = (event.getY() / level.getScale()).toInt; + val breaks = new scala.util.control.Breaks + import breaks.{break, breakable} + breakable{ + for (check <- scala.collection.JavaConversions.asScalaBuffer(level.getBlocks().asInstanceOf[java.util.List[Block]])){ + if (block == check){ + break; + } + if (check.isEnabled()){ + x -= check.getLength(); + } + } + } + block.addThing(makeThing(head, x, mid(0, y - level.getMinZ(), level.getMaxZ() - level.getMinZ()), file.getPath())); + /* + Character c = new Character( reader.nextToken() ); + b.add( new Character( reader.nextToken() ) ); + */ + viewScroll.repaint(); + } else { + // JOptionPane.showMessageDialog( null, "The cursor is not within a block. Either move the cursor or add a block.", "Paintown Editor Error", JOptionPane.ERROR_MESSAGE ); + showError("The cursor is not within a block. Either move the cursor or add a block."); + } + } catch { + case fail:LoadException => { + System.out.println("Could not load " + file); + fail.printStackTrace(); + } + } + } + + all.addMouseListener(new MouseAdapter(){ + override def mouseClicked(clicked:MouseEvent){ + if (clicked.getClickCount() == 2){ + val index = all.locationToIndex(clicked.getPoint()); + val file = get(files, index); + addThing(file); + dialog.setVisible(false); + } + } + }); + + add.addActionListener(new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + val index = all.getSelectedIndex(); + if (index != -1){ + val file = get(files, index); + addThing(file); + dialog.setVisible(false); + } + } + }); + } + + def mousePressed(event:MouseEvent){ + if (leftClick(event)){ + if (selected != null){ + selected.setSelected(false); + } + selected = null; + selectThing(event); + } else if (rightClick(event)){ + showAddObjectPopup(event); + } + } + + def mouseExited(event:MouseEvent){ + if (selected != null){ + // selected = null; + viewScroll.repaint(); + } + } + + def findThingAt(event:MouseEvent):Thing = { + level.findThing((event.getX() / level.getScale()).toInt, + (event.getY() / level.getScale()).toInt); + } + + def mouseClicked(event:MouseEvent){ + } + + def mouseEntered(event:MouseEvent){ + } + + def mouseReleased(event:MouseEvent){ + if (selected != null ){ + // selected = null; + viewScroll.repaint(); + } + } + } + + val mousey = new Mouser(); + + view.addMouseMotionListener(mousey ); + view.addMouseListener(mousey ); + view.addMouseListener(new MouseAdapter(){ + override def mousePressed(event:MouseEvent){ + /* force focus to move to the view */ + view.requestFocusInWindow(); + } + }); + + val tabbed = engine.find("tabbed").asInstanceOf[JTabbedPane]; + val holder = Box.createVerticalBox(); + val blocks = Box.createVerticalBox(); + holder.add(new JScrollPane(blocks)); + holder.add(new JSeparator()); + + class ObjectList extends ListModel[Thing] { + /* list listeners */ + var listeners:List[ListDataListener] = List[ListDataListener]() + var things:List[Thing] = List[Thing]() + var current:Block = null + + def update(thing:Thing){ + val count = 0; + for (current <- things){ + if (current == thing){ + contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, count, count)); + } + } + } + + def setBlock(block:Block){ + current = block; + if (block == null){ + this.things = List[Thing]() + } else { + this.things = toScalaList(block.getThings().asInstanceOf[java.util.List[Thing]]); + for (thing <- things){ + thing.addListener(new Lambda1(){ + override def invoke(o:Object):Object = { + update(o.asInstanceOf[Thing]) + null + } + }) + } + } + + contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, 999999)); + } + + def contentsChanged(event:ListDataEvent){ + for (listener <- listeners){ + listener.contentsChanged(event); + } + } + + /* + public void update( int index ){ + ListDataEvent event = new ListDataEvent( this, ListDataEvent.CONTENTS_CHANGED, index, index + 1 ); + for ( Iterator it = listeners.iterator(); it.hasNext(); ){ + ListDataListener l = (ListDataListener) it.next(); + l.contentsChanged( event ); + } + } + */ + + def getBlock():Block = { + current + } + + override def addListDataListener(listener:ListDataListener){ + this.listeners = this.listeners :+ listener + } + + override def getElementAt(index:Int) = { + get(things, index) + } + + override def getSize():Int = { + this.things.size; + } + + override def removeListDataListener(listener:ListDataListener){ + listeners = listeners diff List(listener) + } + } + + val objectList = new ObjectList(); + // final JList currentObjects = new JList( objectList ); + val blockObjectsEngine = new SwingEngine("block-objects.xml"); + // holder.add( new JLabel( "Objects" ) ); + // holder.add( new JScrollPane( currentObjects ) ); + val objectsAdd = blockObjectsEngine.find( "add" ).asInstanceOf[JButton]; + val objectsDelete = blockObjectsEngine.find( "delete" ).asInstanceOf[JButton]; + val objectsAddRandom = blockObjectsEngine.find( "add-random" ).asInstanceOf[JButton]; + val objectsDeleteAll = blockObjectsEngine.find( "delete-all" ).asInstanceOf[JButton]; + val currentObjects = blockObjectsEngine.find( "current" ).asInstanceOf[JList[Thing]]; + currentObjects.setModel(objectList); + holder.add(blockObjectsEngine.getRootComponent().asInstanceOf[JPanel]); + + holder.add(Box.createVerticalGlue()); + + objectsDelete.addActionListener(new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + val block = objectList.getBlock(); + val thing = currentObjects.getSelectedValue().asInstanceOf[Thing]; + if (thing != null && block != null){ + mousey.setSelected(null); + block.removeThing(thing); + objectList.setBlock(block); + viewScroll.repaint(); + } + } + }); + + objectsDeleteAll.addActionListener(new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + val block = objectList.getBlock(); + if (block != null){ + mousey.setSelected( null ); + block.removeAllThings(); + objectList.setBlock(block); + viewScroll.repaint(); + } + } + }); + + objectsAdd.addActionListener(new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + try{ + if (objectList.getBlock() == null){ + throw new EditorException("Select a block"); + } + mousey.showAddObject(objectList.getBlock()); + } catch { + case fail:EditorException => { + showError(fail); + } + } + } + }); + + objectsAddRandom.addActionListener(new AbstractAction(){ + private val thingsToAdd = 4; + private val makeName = new RandomNameAction("boys.txt"){ + override def actionPerformed(event:ActionEvent){ + } + }; + + private def randomX():Int = { + if (Math.random > 0.5){ + -((Math.random * 640) + 320).toInt; + } else { + (Math.random * 800).toInt + 350; + } + } + + private def randomY():Int = { + (Math.random * (level.getMaxZ() - level.getMinZ())).toInt; + } + + private def randomHealth():Int = { + (Math.random * 60).toInt + 20; + } + + private def make():Character = { + val choose = objectsModel.getElementAt((Math.random * (objectsModel.getSize())).toInt) + val temp = new Token(); + temp.addToken(new Token("character")); + temp.addToken(("name" :: "TempName" :: List[String]()).toArray) + temp.addToken(("coords" :: randomX().toString :: randomY().toString :: List[String]()).toArray) + temp.addToken(("health" :: randomHealth().toString :: List[String]()).toArray) + temp.addToken(("path" :: choose.getPath() :: List[String]()).toArray) + val guy = new Character(temp); + guy.setMap((Math.random * guy.getMaxMaps()).toInt); + return guy; + } + + override def actionPerformed(event:ActionEvent){ + try{ + if (objectList.getBlock() == null){ + throw new EditorException("Select a block"); + } + for (index <- 1 to thingsToAdd){ + try{ + objectList.getBlock().addThing(make()); + } catch { + case fail:LoadException => { + System.out.println("Ignoring exception"); + fail.printStackTrace(); + } + } + } + objectList.setBlock(objectList.getBlock()); + viewScroll.repaint(); + } catch { + case fail:EditorException => { + showError(fail); + } + } + } + }); + + /* if an object is selected highlight it and scroll over to it */ + currentObjects.addListSelectionListener(new ListSelectionListener(){ + def valueChanged(event:ListSelectionEvent){ + val thing = currentObjects.getSelectedValue() + if (mousey.getSelected() != null){ + val old = mousey.getSelected(); + old.setSelected(false); + level.findBlock(old).setHighlight(false); + } + thing.setSelected(true); + mousey.setSelected(thing); + + /* the current X position within the world */ + var currentX = 0; + val block = level.findBlock(thing); + block.setHighlight(true); + + val breaks = new scala.util.control.Breaks + import breaks.{break, breakable} + + /* calculate absolute X position of the selected thing */ + breakable{ + for (next <- scala.collection.JavaConversions.asScalaBuffer(level.getBlocks().asInstanceOf[java.util.List[Block]])){ + if (next == block){ + break; + } + if (next.isEnabled()){ + currentX += next.getLength(); + } + } + } + + currentX += thing.getX(); + /* show the object in the center of the view */ + val move = (currentX * level.getScale() - viewScroll.getHorizontalScrollBar().getVisibleAmount() / 2).toInt; + + /* scroll over to the selected thing */ + // viewScroll.getHorizontalScrollBar().setValue( move ); + smoothScroll(viewScroll.getHorizontalScrollBar(), viewScroll.getHorizontalScrollBar().getValue(), move); + + viewScroll.repaint(); + } + }); + + currentObjects.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete"); + currentObjects.getActionMap().put("delete", new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + val thing = currentObjects.getSelectedValue() + if (thing != null){ + mousey.setSelected(null); + val block = level.findBlock(thing); + block.removeThing(thing); + objectList.setBlock(block); + viewScroll.repaint(); + } + } + }); + + currentObjects.addMouseListener(new MouseAdapter() { + override def mouseClicked(clicked:MouseEvent){ + if (clicked.getClickCount() == 2){ + val thing = currentObjects.getSelectedValue() + editSelected(thing); + } + } + }); + + /* so the user can click on the scrolly pane */ + // viewScroll.setFocusable( true ); + view.setFocusable(true); + + view.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete"); + + /* ctrl-c */ + view.getInputMap().put( KeyStroke.getKeyStroke( KeyEvent.VK_C, 2 ), "copy" ); + /* ctrl-v */ + view.getInputMap().put( KeyStroke.getKeyStroke( KeyEvent.VK_V, 2 ), "paste" ); + /* ctrl-b */ + view.getInputMap().put( KeyStroke.getKeyStroke( KeyEvent.VK_B, 2 ), "change-boy-name" ); + view.getInputMap().put( KeyStroke.getKeyStroke( KeyEvent.VK_G, 2 ), "change-girl-name" ); + + currentObjects.getInputMap().put( KeyStroke.getKeyStroke( KeyEvent.VK_B, 2 ), "change-boy-name" ); + currentObjects.getInputMap().put( KeyStroke.getKeyStroke( KeyEvent.VK_G, 2 ), "change-girl-name" ); + + view.getActionMap().put( "delete", new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + if (mousey.getSelected() != null){ + val block = level.findBlock(mousey.getSelected()); + block.removeThing(mousey.getSelected()); + mousey.setSelected(null); + objectList.setBlock(block); + viewScroll.repaint(); + } + } + }); + + view.getActionMap().put( "copy", new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + // System.out.println( "Copy object" ); + if (mousey.getSelected() != null){ + setCopy(mousey.getSelected().copy()); + } + } + }); + + val changeBoy = new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + if (mousey.getSelected() != null && mousey.getSelected().isInstanceOf[Character]){ + val guy = mousey.getSelected().asInstanceOf[Character]; + guy.setName(NewEditor.this.generateBoysName()); + } + } + }; + + val changeGirl = new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + if ( mousey.getSelected() != null && mousey.getSelected().isInstanceOf[Character]){ + val guy = mousey.getSelected().asInstanceOf[Character]; + guy.setName(NewEditor.this.generateGirlsName() ); + } + } + }; + + view.getActionMap().put( "change-boy-name", changeBoy ); + view.getActionMap().put( "change-girl-name", changeGirl ); + currentObjects.getActionMap().put( "change-boy-name", changeBoy ); + currentObjects.getActionMap().put( "change-girl-name", changeGirl ); + + view.getActionMap().put( "paste", new AbstractAction(){ + def calculateLength(blocks:List[Block]):Int = { + var total = 0 + for (block <- blocks){ + total += block.getLength(); + } + return total; + } + + override def actionPerformed(event:ActionEvent){ + /* middle of the current screen */ + val x = (viewScroll.getHorizontalScrollBar().getValue() / level.getScale() + viewScroll.getHorizontalScrollBar().getVisibleAmount() / level.getScale() / 2 ).toInt; + /* in between the min and max z lines */ + val y = ((level.getMaxZ() - level.getMinZ()) / 2).toInt; + val block = level.findBlock(x); + if (block != null && getCopy() != null){ + val copy = getCopy().copy(); + /* x has to be relative to the beginning of the block */ + copy.setX(x - calculateLength(toScalaList(level.getBlocks().subList(0, level.getBlocks().indexOf(block)).asInstanceOf[java.util.List[Block]]))); + copy.setY(y); + block.addThing(copy); + objectList.setBlock(block); + viewScroll.repaint(); + } else { + println("No block found at " + x); + } + } + }); + + tabbed.add("Blocks", holder); + + val objectEngine = new SwingEngine("objects.xml"); + tabbed.add("Objects", objectEngine.getRootComponent().asInstanceOf[JComponent]); + + // final JList objects = new JList( allowableObjects ); + val objects = objectEngine.find( "objects" ).asInstanceOf[JList[File]]; + /* objectsModel is declared way up top */ + objects.setModel(objectsModel); + + { + val add = objectEngine.find( "add" ).asInstanceOf[JButton]; + val remove = objectEngine.find( "delete" ).asInstanceOf[JButton]; + + add.addActionListener(new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + val chooser = new RelativeFileChooser(NewEditor.this, "Choose a file", Data.getDataPath()); + val ret = chooser.open(); + if (ret == RelativeFileChooser.OK ){ + val path = chooser.getPath(); + objectsModel.add(new File(path)); + } + } + }); + + remove.addActionListener( new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + val index = objects.getSelectedIndex(); + if (index != -1){ + objectsModel.remove(index); + } + } + }); + } + + val levelEngine = new SwingEngine( "level.xml" ); + // debugSwixml( levelEngine ); + val levelPane = levelEngine.getRootComponent().asInstanceOf[JPanel]; + tabbed.add("Level", levelPane); + + val levelMinZ = levelEngine.find( "min-z" ).asInstanceOf[JSpinner]; + val levelMaxZ = levelEngine.find( "max-z" ).asInstanceOf[JSpinner]; + val atmosphere = levelEngine.find( "atmosphere" ).asInstanceOf[JComboBox[String]]; + val levelBackground = levelEngine.find( "background" ).asInstanceOf[JTextField]; + val levelDescription = levelEngine.find("description").asInstanceOf[JTextField]; + val levelChangeBackground = levelEngine.find( "change-background" ).asInstanceOf[JButton]; + val frontPanelsData = new java.util.Vector[String](); + val frontPanels = levelEngine.find( "front-panels" ).asInstanceOf[JList[String]]; + frontPanels.setListData(frontPanelsData); + val backPanelsData = new java.util.Vector[String](); + val backPanels = levelEngine.find( "back-panels" ).asInstanceOf[JList[String]]; + val order = levelEngine.find( "order" ).asInstanceOf[JTextArea]; + val pickOrder = levelEngine.find( "pick-order" ).asInstanceOf[JComboBox[String]]; + val backgroundParallax = levelEngine.find( "background-parallax-slider" ).asInstanceOf[JSlider]; + val backgroundAmount = levelEngine.find( "background-parallax-amount" ).asInstanceOf[JLabel]; + val foregroundParallax = levelEngine.find( "foreground-parallax-slider" ).asInstanceOf[JSlider]; + val foregroundAmount = levelEngine.find( "foreground-parallax-amount" ).asInstanceOf[JLabel]; + + foregroundAmount.setText(level.getForegroundParallax().toString); + backgroundAmount.setText(level.getBackgroundParallax().toString); + backgroundParallax.setValue(level.getBackgroundParallax().toInt); + foregroundParallax.setValue((level.getForegroundParallax() * 10).toInt); + + backgroundParallax.addChangeListener( new ChangeListener(){ + override def stateChanged(event:ChangeEvent){ + level.setBackgroundParallax(backgroundParallax.getValue().toDouble); + backgroundAmount.setText(level.getBackgroundParallax().toString); + } + }); + + foregroundParallax.addChangeListener( new ChangeListener(){ + override def stateChanged(event:ChangeEvent){ + level.setForegroundParallax(foregroundParallax.getValue().toDouble / 10.0); + foregroundAmount.setText(level.getForegroundParallax().toString); + } + }); + + levelDescription.setText(level.getDescription()); + levelDescription.addActionListener(new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + level.setDescription(levelDescription.getText()); + } + }); + + atmosphere.addItem( null ); + atmosphere.addItem( "rain" ); + atmosphere.addItem( "snow" ); + atmosphere.addItem( "night" ); + atmosphere.addItem( "fog" ); + + atmosphere.setSelectedItem( level.getAtmosphere() ); + + atmosphere.addActionListener( new ActionListener(){ + override def actionPerformed(event:ActionEvent){ + level.setAtmosphere(atmosphere.getSelectedItem().asInstanceOf[String]); + } + }); + + atmosphere.setEditable( false ); + + /* FIXME: the type parameter should probably be something other than Object */ + class BackPanelCombo(data:java.util.Vector[String]) extends ComboBoxModel[String] { + var selected:String = null + var listeners:List[ListDataListener] = List[ListDataListener]() + + def getSelectedItem() = { + selected + } + + def update(){ + selected = null + val event = new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, 99999) + for (listener <- listeners){ + listener.contentsChanged(event); + } + } + + /* FIXME: what is going on here? why does scala force us to give + * a type parameter for ComboBoxModel and then the setSelectedItem + * method takes an Object? Looks like someone screwed up in Java-land.. + */ + def setSelectedItem(item:Object){ + selected = item.asInstanceOf[String] + } + + override def addListDataListener(listener:ListDataListener){ + listeners = listeners :+ listener + } + + override def getElementAt(index:Int) = { + this.data.get(index) + } + + override def getSize():Int = { + this.data.size() + } + + override def removeListDataListener(listener:ListDataListener){ + this.listeners = this.listeners diff List(listener) + } + } + + val comboModel = new BackPanelCombo(backPanelsData); + pickOrder.setModel(comboModel); + + def setOrderText(){ + val orderText = new StringBuffer(); + for (order <- toScalaList[java.lang.Integer](level.getBackPanelOrder().asInstanceOf[java.util.List[java.lang.Integer]])){ + val name = level.getBackPanelName(order.intValue()) + orderText.append(name).append("\n"); + } + order.setText(orderText.toString()); + }; + + { + val add = levelEngine.find("add-order").asInstanceOf[JButton]; + val remove = levelEngine.find( "remove-order" ).asInstanceOf[JButton]; + add.addActionListener( new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + val path = pickOrder.getSelectedItem().asInstanceOf[String]; + if (path != null){ + level.addBackPanelOrder(path); + setOrderText(); + viewScroll.repaint(); + } + } + }); + + remove.addActionListener( new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + level.removeLastOrder(); + setOrderText() + viewScroll.repaint(); + } + }); + } + + { /* force scope */ + val add = levelEngine.find( "add-front-panel" ).asInstanceOf[JButton]; + add.addActionListener( new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + val chooser = new RelativeFileChooser(NewEditor.this, "Choose a file", Data.getDataPath()); + val ret = chooser.open(); + if (ret == RelativeFileChooser.OK){ + try{ + val path:String = chooser.getPath(); + level.addFrontPanel(path); + frontPanelsData.add(path); + frontPanels.setListData(frontPanelsData); + viewScroll.repaint(); + } catch { + case fail:LoadException => { + fail.printStackTrace(); + } + } + } + } + }); + + val remove = levelEngine.find( "delete-front-panel" ).asInstanceOf[JButton]; + remove.addActionListener( new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + if (frontPanels.getSelectedValue() != null){ + val path = frontPanels.getSelectedValue().asInstanceOf[String]; + level.removeFrontPanel(path); + frontPanelsData.remove(path); + frontPanels.setListData(frontPanelsData); + viewScroll.repaint(); + } + } + }); + } + + { /* force scope */ + val add = levelEngine.find( "add-back-panel" ).asInstanceOf[JButton]; + add.addActionListener( new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + val chooser = new RelativeFileChooser(NewEditor.this, "Choose a file", Data.getDataPath()); + val ret = chooser.open(); + if (ret == RelativeFileChooser.OK){ + try{ + val path = chooser.getPath(); + level.addBackPanel(path); + backPanelsData.add(path); + backPanels.setListData(backPanelsData); + comboModel.update(); + viewScroll.repaint(); + } catch { + case fail:LoadException => { + fail.printStackTrace(); + } + } + } + } + }); + + val remove = levelEngine.find( "delete-back-panel" ).asInstanceOf[JButton]; + remove.addActionListener( new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + if (backPanels.getSelectedValue() != null){ + val path = backPanels.getSelectedValue().asInstanceOf[String]; + level.removeBackPanel(path); + backPanelsData.remove(path); + backPanels.setListData(backPanelsData); + setOrderText() + comboModel.update(); + viewScroll.repaint(); + } + } + }); + } + + levelMinZ.setModel(new SpinnerNumberModel()); + levelMinZ.addChangeListener(new ChangeListener(){ + override def stateChanged(event:ChangeEvent){ + val spinner = event.getSource().asInstanceOf[JSpinner]; + val i = spinner.getValue().asInstanceOf[java.lang.Integer]; + level.setMinZ(i.intValue()); + viewScroll.repaint(); + } + }); + + levelMaxZ.setModel( new SpinnerNumberModel() ); + levelMaxZ.addChangeListener( new ChangeListener(){ + override def stateChanged(event:ChangeEvent){ + val spinner = event.getSource().asInstanceOf[JSpinner]; + val i = spinner.getValue().asInstanceOf[java.lang.Integer]; + level.setMaxZ(i.intValue()); + viewScroll.repaint(); + } + }); + + levelBackground.addActionListener(new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + level.loadBackground(levelBackground.getText()); + viewScroll.repaint(); + } + }); + + levelChangeBackground.addActionListener(new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + val chooser = new RelativeFileChooser(NewEditor.this, "Choose a file", Data.getDataPath()); + val ret = chooser.open(); + if (ret == RelativeFileChooser.OK){ + val path = chooser.getPath(); + level.loadBackground(path); + levelBackground.setText(path); + viewScroll.repaint(); + } + } + }); + + val introText = levelEngine.find("intro").asInstanceOf[JTextField] + introText.setText(level.getIntro()) + introText.addActionListener(new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + level.setIntro(introText.getText()) + } + }); + + val introFile = levelEngine.find("intro-pick").asInstanceOf[JButton] + introFile.addActionListener(new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + val chooser = new RelativeFileChooser(NewEditor.this, "Choose a file", Data.getDataPath()); + val ret = chooser.open(); + if (ret == RelativeFileChooser.OK ){ + val path = chooser.getPath(); + level.setIntro(path); + introText.setText(path); + } + } + }); + + val endingText = levelEngine.find("ending").asInstanceOf[JTextField] + endingText.setText(level.getEnding()) + endingText.addActionListener(new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + level.setEnding(endingText.getText()) + } + }); + + val endingFile = levelEngine.find("ending-pick").asInstanceOf[JButton] + endingFile.addActionListener(new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + val chooser = new RelativeFileChooser(NewEditor.this, "Choose a file", Data.getDataPath()); + val ret = chooser.open(); + if (ret == RelativeFileChooser.OK ){ + val path = chooser.getPath(); + level.setEnding(path); + endingText.setText(path); + } + } + }); + + /* initialize all the other crap for a level */ + def loadLevelProperties(level:Level){ + levelMinZ.setValue( new Integer( level.getMinZ() ) ); + levelMaxZ.setValue( new Integer( level.getMaxZ() ) ); + levelBackground.setText( level.getBackgroundFile() ); + frontPanelsData.clear(); + frontPanelsData.addAll(level.getFrontPanelNames().asInstanceOf[java.util.List[String]]); + frontPanels.setListData( frontPanelsData ); + backPanelsData.clear(); + backPanelsData.addAll(level.getBackPanelNames().asInstanceOf[java.util.List[String]]); + backPanels.setListData( backPanelsData ); + + setOrderText(); + comboModel.update(); + }; + + /* mess around with layout nonsense */ + val layout = new GridBagLayout(); + viewContainer.setLayout(layout); + val constraints = new GridBagConstraints(); + constraints.fill = GridBagConstraints.BOTH; + + constraints.weightx = 1; + constraints.weighty = 1; + layout.setConstraints(viewScroll, constraints); + view.setBorder(BorderFactory.createLineBorder(new Color(255, 0, 0))); + viewContainer.add(viewScroll); + + def setupBlocks(level:Level){ + def editBlockProperties(block:Block, done: () => Unit){ + val dialog = new JDialog(NewEditor.this, "Edit"); + dialog.setSize(200, 200); + val engine = new SwingEngine("block.xml"); + dialog.getContentPane().add(engine.getRootComponent().asInstanceOf[JPanel]); + + val length = engine.find( "length" ).asInstanceOf[JTextField]; + val finish = engine.find( "finish" ).asInstanceOf[JTextField]; + val isFinish = engine.find( "is-finish" ).asInstanceOf[JCheckBox]; + val id = engine.find("id").asInstanceOf[JSpinner]; + val isContinuous = engine.find( "is-continuous" ).asInstanceOf[JCheckBox]; + val save = engine.find( "save" ).asInstanceOf[JButton]; + val close = engine.find( "close" ).asInstanceOf[JButton]; + + length.setText(block.getLength().toString) + isContinuous.setSelected(block.isContinuous()); + id.setValue(block.getId()); + isFinish.setSelected(block.isFinish()); + finish.setEnabled(block.isFinish()); + finish.setText(block.getFinish().toString); + isFinish.addActionListener( new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + finish.setEnabled(isFinish.isSelected()); + } + }); + + save.addActionListener( new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + block.setLength(java.lang.Integer.parseInt(length.getText())) + block.setId(id.getValue().asInstanceOf[java.lang.Integer].intValue()); + if (isFinish.isSelected()){ + block.setFinish(java.lang.Integer.parseInt(finish.getText())); + } else { + block.setFinish(0); + } + block.setContinuous(isContinuous.isSelected()); + done() + dialog.setVisible(false); + } + }); + + close.addActionListener( new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + dialog.setVisible(false); + } + }); + + dialog.setVisible( true ); + } + + def scrollToBlock(block:Block){ + var length = 0; + val breaks = new scala.util.control.Breaks + import breaks.{break, breakable} + breakable{ + for (next <- toScalaList(level.getBlocks().asInstanceOf[java.util.List[Block]])){ + if (next == block ){ + break; + } + if (next.isEnabled() ){ + length += next.getLength(); + } + } + } + smoothScroll(viewScroll.getHorizontalScrollBar(), viewScroll.getHorizontalScrollBar().getValue(), (length * level.getScale() - 15).toInt); + // viewScroll.getHorizontalScrollBar().setValue( (int)(length * level.getScale() - 10) ); + } + + blocks.removeAll(); + var n = 1; + var total = 0; + for (block <- toScalaList(level.getBlocks().asInstanceOf[java.util.List[Block]])){ + val stuff = Box.createHorizontalBox(); + val check = new JCheckBox( new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + val source = event.getSource().asInstanceOf[JCheckBox]; + block.setEnabled(source.isSelected()); + view.revalidate(); + viewScroll.repaint(); + } + }); + + check.setToolTipText("Check this box to make the block appear in the game"); + + check.setSelected(true); + stuff.add(check); + val button = new JButton("Block " + n + " : " + block.getLength()); + button.addActionListener( new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + if (block.isEnabled()){ + scrollToBlock(block); + objectList.setBlock(block); + } + } + }); + stuff.add(button); + stuff.add(Box.createHorizontalStrut(3)); + + val edit = new JButton( "Edit" ); + val xnum = n; + edit.addActionListener( new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + editBlockProperties(block, () => { + button.setText( "Block " + xnum + " : " + block.getLength() ); + view.revalidate(); + viewScroll.repaint(); + }) + } + }); + stuff.add(edit); + stuff.add(Box.createHorizontalStrut(3)); + + val erase = new JButton("Delete"); + erase.addActionListener( new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + mousey.setSelected(null); + objectList.setBlock(null); + level.getBlocks().remove(block); + setupBlocks(level) + view.repaint(); + } + }); + stuff.add(erase); + + stuff.add(Box.createHorizontalGlue()); + blocks.add(stuff); + + total += block.getLength(); + n += 1; + } + + blocks.add(Box.createVerticalGlue()); + val addf = Box.createHorizontalBox() + val add = new JButton( "Add block" ); + add.addActionListener( new AbstractAction(){ + override def actionPerformed(event:ActionEvent){ + val b = new Block(); + level.getBlocks().asInstanceOf[java.util.List[Block]].add(b); + setupBlocks(level) + view.revalidate(); + viewScroll.repaint(); + } + }); + addf.add(add); + addf.add(Box.createHorizontalGlue()); + blocks.add(addf); + val f = Box.createHorizontalBox(); + f.add(new JLabel("Total length " + total)); + f.add(Box.createHorizontalGlue()); + blocks.add(f); + blocks.revalidate(); + blocks.repaint(); + } + + setupBlocks(level) + loadLevelProperties(level); + + val scroll = engine.find( "level-scale" ).asInstanceOf[JSlider]; + val scale = engine.find( "scale" ).asInstanceOf[JLabel]; + scroll.addChangeListener( new ChangeListener(){ + override def stateChanged(event:ChangeEvent){ + level.setScale(scroll.getValue().toDouble * 2.0 / scroll.getMaximum()); + scale.setText("Scale: " + level.getScale()); + view.revalidate(); + viewScroll.repaint(); + } + }); + + val split = engine.getRootComponent().asInstanceOf[JSplitPane] + split.setContinuousLayout(true) + split + } + + /* FIXME: make this search in data for any .txt file that matches a + * (character) or (item) ... definition + */ + def defaultObjects():List[File] = { + List[File]() :+ + new File("chars/angel/angel.txt") :+ + new File("chars/billy/billy.txt") :+ + new File("chars/eiji/eiji.txt") :+ + new File("chars/heavy/heavy.txt") :+ + new File("chars/jhun/jhun.txt") :+ + new File("chars/joe/joe.txt") :+ + new File("chars/punk/punk.txt") :+ + new File("chars/ralf/ralf.txt") :+ + new File("chars/robert/robert.txt") :+ + new File("chars/rugal/rugal.txt") :+ + new File("chars/shermie/shermie.txt") :+ + new File("chars/yamazaki/yamazaki.txt") :+ + new File("chars/yashiro/yashiro.txt") :+ + new File("misc/apple/apple.txt") :+ + new File("misc/cake/cake.txt") :+ + new File("misc/chicken/chicken.txt") :+ + new File("misc/cat/cat.txt") + } + + def dataPath(file:File):File = { + new File(Data.getDataPath().getPath() + "/" + file.getPath()); + } + + def userSelectFile(title:String):File = { + val chooser = new JFileChooser(new File(".")); + chooser.setDialogTitle(title); + chooser.showOpenDialog(NewEditor.this) match { + case JFileChooser.APPROVE_OPTION => chooser.getSelectedFile() + case _ => null + } + } + + def getCopy():Thing = { + copy + } + + def setCopy(thing:Thing){ + copy = thing + } + + def smoothScroll(scroll:JScrollBar, starting:Int, end:Int){ + new Thread(){ + override def run(){ + var begin:Int = starting; + for (index <- 0 to 5){ + val to = (begin + end) / 2; + scroll.setValue(to); + begin = to; + try{ + Thread.sleep(20); + } catch { + case e:Exception => {} + } + } + scroll.setValue(end); + } + }.start(); + } + + def generateBoysName():String = { + return new RandomNameAction("boys.txt"){ + override def actionPerformed(event:ActionEvent){ + } + }.generateName(); + } + + def generateGirlsName():String = { + return new RandomNameAction( "girls.txt" ){ + override def actionPerformed(event:ActionEvent){ + } + }.generateName(); + } + + def showError(message:String){ + JOptionPane.showMessageDialog(null, message, "Paintown Editor Error", JOptionPane.ERROR_MESSAGE); + } + + def showError(message:EditorException){ + showError(message.getMessage()); + } +} + +object Editor2 { + def main(args: Array[String]):Unit = { + System.out.println("Current working directory is " + System.getProperty("user.dir")) + val editor = new NewEditor(); + SwingUtilities.invokeLater(new Runnable(){ + def run(){ + editor.setVisible(true); + } + }); + } +} diff --git a/editor/src/main/scala/com/rafkind/paintown/undo.scala b/editor/src/main/scala/com/rafkind/paintown/undo.scala new file mode 100644 index 000000000..a88453138 --- /dev/null +++ b/editor/src/main/scala/com/rafkind/paintown/undo.scala @@ -0,0 +1,63 @@ +package com.rafkind.paintown + +/* Stores actions to undo. Each action has a description associated with it as a string. + * Actions popped off the undo stack are added to the redo stack and vice-versa. + * + * For now infinite undo is offered. + */ + +object Undo{ + type thunk = () => Unit + + /* Keep track of the menu item that will display the latest undo thing */ + var undoMenuItem:javax.swing.JMenuItem = null + + var undos = List[Undo]() + var redos = List[Undo]() + + def setUndoMenuItem(menu:javax.swing.JMenuItem):Unit = { + undoMenuItem = menu + } + + def addUndo(what:String, undo: thunk):Unit = { + undos.synchronized{ + if (undoMenuItem != null){ + undoMenuItem.setText("Undo: " + what) + } + undos = Undo(undo, what) :: undos + } + } + + def popUndo():Unit = { + undos.synchronized{ + if (! undos.isEmpty){ + val first = undos.head + undos = undos.tail + first.doit() + redos = first :: redos + if (undoMenuItem != null){ + if (! undos.isEmpty){ + val newFirst = undos.head + undoMenuItem.setText("Undo: " + newFirst.what) + } else { + undoMenuItem.setText("Undo last action") + } + } + } + } + } + + def popRedo():Unit = { + if (! redos.isEmpty){ + val first = redos.head + redos = redos.tail + first.doit() + undos = first :: undos + if (undoMenuItem != null){ + undoMenuItem.setText("Undo: " + first.what) + } + } + } +} + +case class Undo(doit:Undo.thunk, what:String)