/*
* Copyright (C) 2009 Quadduc <quadduc@gmail.com>
*
* This file is part of LateralGM.
* LateralGM is free software and comes with ABSOLUTELY NO WARRANTY.
* See LICENSE for details.
*/
package org.lateralgm.ui.swing.visuals;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.RasterFormatException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import javax.swing.SwingUtilities;
import org.lateralgm.main.Util;
import org.lateralgm.main.UpdateSource.UpdateEvent;
import org.lateralgm.main.UpdateSource.UpdateListener;
import org.lateralgm.resources.Background;
import org.lateralgm.resources.GmObject;
import org.lateralgm.resources.ResourceReference;
import org.lateralgm.resources.Room;
import org.lateralgm.resources.Sprite;
import org.lateralgm.resources.GmObject.PGmObject;
import org.lateralgm.resources.Room.PRoom;
import org.lateralgm.resources.Room.Piece;
import org.lateralgm.resources.Sprite.PSprite;
import org.lateralgm.resources.sub.BackgroundDef;
import org.lateralgm.resources.sub.Instance;
import org.lateralgm.resources.sub.Tile;
import org.lateralgm.resources.sub.BackgroundDef.PBackgroundDef;
import org.lateralgm.resources.sub.Instance.PInstance;
import org.lateralgm.resources.sub.Tile.PTile;
import org.lateralgm.util.ActiveArrayList;
import org.lateralgm.util.ActiveArrayList.ListUpdateEvent;
import org.lateralgm.util.PropertyMap.PropertyUpdateEvent;
import org.lateralgm.util.PropertyMap.PropertyUpdateListener;
public class RoomVisual extends AbstractVisual implements BoundedVisual,UpdateListener
{
protected static final BufferedImage EMPTY_IMAGE = new BufferedImage(16,16,
BufferedImage.TYPE_INT_ARGB);
private final BinVisual binVisual;
private final GridVisual gridVisual;
public final Room room;
// These variables are here to keep the managers from being GC'd.
protected final InstanceVisualListManager ivlm;
protected final TileVisualListManager tvlm;
private final RoomPropertyListener rpl = new RoomPropertyListener();
private final BgDefPropertyListener bdpl = new BgDefPropertyListener();
private EnumSet<Show> show;
private int gridFactor = 1;
private int gridX, gridY;
public enum Show
{
BACKGROUNDS,INSTANCES,TILES,FOREGROUNDS,GRID,VIEWS
}
public RoomVisual(VisualContainer vc, Room r)
{
this(vc,r,EnumSet.range(Show.BACKGROUNDS,Show.GRID));
}
public RoomVisual(VisualContainer vc, Room r, EnumSet<Show> s)
{
super(vc);
room = r;
show = EnumSet.copyOf(s);
binVisual = new BinVisual(vc,128,(Integer) r.get(PRoom.WIDTH),(Integer) r.get(PRoom.HEIGHT));
gridVisual = new GridVisual((Boolean) r.get(PRoom.ISOMETRIC),(Integer) r.get(PRoom.SNAP_X),
(Integer) r.get(PRoom.SNAP_Y));
r.properties.updateSource.addListener(rpl);
ivlm = new InstanceVisualListManager();
tvlm = new TileVisualListManager();
for (BackgroundDef bd : room.backgroundDefs)
{
bd.properties.updateSource.addListener(bdpl);
bd.updateSource.addListener(this);
}
}
public void extendBounds(Rectangle b)
{
b.add(new Rectangle(0,0,(Integer) room.get(PRoom.WIDTH),(Integer) room.get(PRoom.HEIGHT)));
binVisual.extendBounds(b);
}
public void paint(Graphics g)
{
int width = (Integer) room.get(PRoom.WIDTH);
int height = (Integer) room.get(PRoom.HEIGHT);
Graphics g2 = g.create();
g2.clipRect(0,0,width,height);
if (room.get(PRoom.DRAW_BACKGROUND_COLOR))
{
g2.setColor((Color) room.get(PRoom.BACKGROUND_COLOR));
g2.fillRect(0,0,width,height);
}
if (show.contains(Show.BACKGROUNDS)) for (BackgroundDef bd : room.backgroundDefs)
if (shouldPaint(bd,false)) paintBackground(g2,bd,width,height);
// Paint pieces and tiles on the unclipped g, so that they are visible
// even if outside the room
if (show.contains(Show.INSTANCES) || show.contains(Show.TILES)) binVisual.paint(g);
if (show.contains(Show.FOREGROUNDS)) for (BackgroundDef bd : room.backgroundDefs)
if (shouldPaint(bd,true)) paintBackground(g2,bd,width,height);
if (show.contains(Show.GRID))
{
g2.translate(gridX
- (room.get(PRoom.ISOMETRIC) ? (Integer) room.get(PRoom.SNAP_X) * (gridFactor - 1) / 2
: 0),gridY);
gridVisual.paint(g2);
}
g2.dispose();
}
private static boolean shouldPaint(BackgroundDef bd, Boolean fg)
{
if (!(Boolean) bd.properties.get(PBackgroundDef.VISIBLE)) return false;
return fg.equals(bd.properties.get(PBackgroundDef.FOREGROUND));
}
public void setVisible(Show s, boolean v)
{
if (v ? show.add(s) : show.remove(s))
{
if (s == Show.GRID && !v) gridVisual.flush(true);
repaint(null);
}
}
public void setGridFactor(int f)
{
gridFactor = f;
gridVisual.setWidth(gridFactor * (Integer) room.get(PRoom.SNAP_X));
gridVisual.setHeight(gridFactor * (Integer) room.get(PRoom.SNAP_Y));
}
public void setGridOffset(int x, int y)
{
if (gridX == x && gridY == y) return;
gridX = x;
gridY = y;
if (show.contains(Show.GRID)) repaint(null);
}
public void setGridXOffset(int x)
{
if (gridX == x) return;
gridX = x;
if (show.contains(Show.GRID)) repaint(null);
}
public void setGridYOffset(int y)
{
if (gridY == y) return;
gridY = y;
if (show.contains(Show.GRID)) repaint(null);
}
public <P extends Piece>Iterator<P> intersect(Rectangle r, Class<P> p)
{
final Iterator<PieceVisual<P>> vi = binVisual.intersect(r,getVisualClass(p));
return new Iterator<P>()
{
public boolean hasNext()
{
return vi.hasNext();
}
public P next()
{
return vi.next().piece;
}
public void remove()
{
vi.remove();
}
};
}
public boolean intersects(Rectangle r, Piece p)
{
Iterator<Piece> pi = intersect(r);
while (pi.hasNext())
if (pi.next() == p) return true;
return false;
}
@SuppressWarnings("unchecked")
private <P extends Piece, V extends PieceVisual<P>>Class<V> getVisualClass(Class<P> p)
{
if (p == Piece.class) return (Class<V>) PieceVisual.class;
if (p == Instance.class) return (Class<V>) InstanceVisual.class;
if (p == Tile.class) return (Class<V>) TileVisual.class;
throw new IllegalArgumentException();
}
public Iterator<Tile> intersectTiles(Rectangle r)
{
return intersect(r,Tile.class);
}
public Iterator<Instance> intersectInstances(Rectangle r)
{
return intersect(r,Instance.class);
}
public Iterator<Piece> intersect(Rectangle r)
{
return intersect(r,Piece.class);
}
private void paintBackground(Graphics g, BackgroundDef bd, int width, int height)
{
Rectangle c = g.getClipBounds();
ResourceReference<Background> rb = bd.properties.get(PBackgroundDef.BACKGROUND);
Background b = Util.deRef(rb);
if (b == null) return;
BufferedImage bi = b.getDisplayImage();
if (bi == null) return;
boolean stretch = bd.properties.get(PBackgroundDef.STRETCH);
int w = stretch ? width : bi.getWidth();
int h = stretch ? height : bi.getHeight();
boolean tileHoriz = bd.properties.get(PBackgroundDef.TILE_HORIZ);
boolean tileVert = bd.properties.get(PBackgroundDef.TILE_VERT);
int x = bd.properties.get(PBackgroundDef.X);
int y = bd.properties.get(PBackgroundDef.Y);
if (tileHoriz || tileVert)
{
int ncol = 1;
int nrow = 1;
if (tileHoriz)
{
x = 1 + c.x + ((x + w - 1 - c.x) % w) - w;
ncol = 1 + (c.x + c.width - x - 1) / w;
}
if (tileVert)
{
y = 1 + c.y + ((y + h - 1 - c.y) % h) - h;
nrow = 1 + (c.y + c.height - y - 1) / h;
}
for (int row = 0; row < nrow; row++)
for (int col = 0; col < ncol; col++)
g.drawImage(bi,(x + w * col),(y + h * row),w,h,null);
}
else
g.drawImage(bi,x,y,w,h,null);
}
private abstract class PieceVisual<P extends Piece> extends VisualBox
{
protected final ResourceUpdateListener rul = new ResourceUpdateListener();
public final P piece;
private boolean invalid;
public PieceVisual(P p)
{
super(binVisual);
piece = p;
}
protected abstract void validate();
protected final void invalidate()
{
if (invalid) return;
invalid = true;
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
try
{
if (invalid) validate();
}
finally
{
invalid = false;
}
}
});
}
protected class ResourceUpdateListener implements UpdateListener
{
public void updated(UpdateEvent e)
{
invalidate();
}
}
}
private class InstanceVisual extends PieceVisual<Instance>
{
private BufferedImage image;
private final InstancePropertyListener ipl = new InstancePropertyListener();
public InstanceVisual(Instance i)
{
super(i);
i.updateSource.addListener(rul);
i.properties.updateSource.addListener(ipl);
validate();
}
@Override
protected void validate()
{
ResourceReference<GmObject> ro = piece.properties.get(PInstance.OBJECT);
GmObject o = ro == null ? null : ro.get();
ResourceReference<Sprite> rs = null;
if (o != null) rs = o.get(PGmObject.SPRITE);
Sprite s = rs == null ? null : rs.get();
image = s == null ? null : s.getDisplayImage();
if (image == null) image = EMPTY_IMAGE;
binVisual.setDepth(this,o == null ? 0 : (Integer) o.get(PGmObject.DEPTH));
Point p = piece.getPosition();
if (s != null)
p.translate(-(Integer) s.get(PSprite.ORIGIN_X),-(Integer) s.get(PSprite.ORIGIN_Y));
setBounds(new Rectangle(p.x,p.y,image.getWidth(),image.getHeight()));
}
public void paint(Graphics g)
{
if (show.contains(Show.INSTANCES)) g.drawImage(image,0,0,null);
}
@Override
public void remove()
{
piece.updateSource.removeListener(rul);
piece.properties.updateSource.removeListener(ipl);
image = null;
super.remove();
}
class InstancePropertyListener extends PropertyUpdateListener<PInstance>
{
@Override
public void updated(PropertyUpdateEvent<PInstance> e)
{
switch (e.key)
{
case X:
case Y:
case OBJECT:
invalidate();
break;
}
}
}
}
private class TileVisual extends PieceVisual<Tile>
{
private BufferedImage image;
private final TilePropertyListener tpl = new TilePropertyListener();
public TileVisual(Tile t)
{
super(t);
t.updateSource.addListener(rul);
t.properties.updateSource.addListener(tpl);
validate();
}
@Override
protected void validate()
{
ResourceReference<Background> rb = piece.properties.get(PTile.BACKGROUND);
Background b = rb == null ? null : rb.get();
BufferedImage bi = b == null ? null : b.getDisplayImage();
if (bi == null)
image = EMPTY_IMAGE;
else
{
Point p = piece.getBackgroundPosition();
Dimension d = piece.getSize();
try
{
image = bi.getSubimage(p.x,p.y,d.width,d.height);
}
catch (RasterFormatException e)
{
image = EMPTY_IMAGE;
}
}
binVisual.setDepth(this,piece.getDepth());
Rectangle r = new Rectangle(piece.getRoomPosition(),piece.getSize());
setBounds(r);
}
public void paint(Graphics g)
{
if (show.contains(Show.TILES)) g.drawImage(image,0,0,null);
}
@Override
public void remove()
{
piece.updateSource.removeListener(rul);
piece.properties.updateSource.removeListener(tpl);
image = null;
super.remove();
}
class TilePropertyListener extends PropertyUpdateListener<PTile>
{
@Override
public void updated(PropertyUpdateEvent<PTile> e)
{
switch (e.key)
{
case DEPTH:
case ROOM_X:
case ROOM_Y:
invalidate();
break;
}
}
}
}
private class InstanceVisualListManager extends VisualListManager<Instance,InstanceVisual>
{
public InstanceVisualListManager()
{
super(room.instances);
}
protected InstanceVisual createVisual(Instance t)
{
return new InstanceVisual(t);
}
protected Instance getT(InstanceVisual v)
{
return v.piece;
}
}
private class TileVisualListManager extends VisualListManager<Tile,TileVisual>
{
public TileVisualListManager()
{
super(room.tiles);
}
protected TileVisual createVisual(Tile t)
{
return new TileVisual(t);
}
protected Tile getT(TileVisual v)
{
return v.piece;
}
}
private abstract static class VisualListManager<T, V extends VisualBox> implements UpdateListener
{
public final ActiveArrayList<T> tList;
private final ArrayList<V> vList;
public VisualListManager(ActiveArrayList<T> tl)
{
tList = tl;
vList = new ArrayList<V>(tl.size());
for (T t : tl)
vList.add(createVisual(t));
tl.updateSource.addListener(this);
}
protected abstract V createVisual(T t);
protected abstract T getT(V v);
public void updated(UpdateEvent e)
{
ListUpdateEvent lue = (ListUpdateEvent) e;
switch (lue.type)
{
case ADDED:
for (int i = lue.fromIndex; i <= lue.toIndex; i++)
{
T t = tList.get(i);
V v = createVisual(t);
vList.add(i,v);
}
break;
case REMOVED:
for (int i = lue.toIndex; i >= lue.fromIndex; i--)
vList.remove(i).remove();
break;
case CHANGED:
HashSet<T> ts = new HashSet<T>(tList);
HashMap<T,V> tm = new HashMap<T,V>(Math.min(vList.size(),tList.size()));
for (V v : vList)
{
T t = getT(v);
if (ts.contains(t))
tm.put(t,v);
else
v.remove();
}
vList.clear();
for (T t : tList)
{
V v = tm.get(t);
vList.add(v == null ? createVisual(t) : v);
}
}
assert tList.size() == vList.size();
}
}
private class RoomPropertyListener extends PropertyUpdateListener<PRoom>
{
public void updated(PropertyUpdateEvent<PRoom> e)
{
switch (e.key)
{
case BACKGROUND_COLOR:
if (room.get(PRoom.DRAW_BACKGROUND_COLOR)) repaint(null);
break;
case DRAW_BACKGROUND_COLOR:
repaint(null);
break;
case ENABLE_VIEWS:
if (show.contains(Show.VIEWS)) repaint(null);
break;
case ISOMETRIC:
gridVisual.setRhombic((Boolean) room.get(PRoom.ISOMETRIC));
if (show.contains(Show.GRID)) repaint(null);
break;
case SNAP_X:
gridVisual.setWidth(gridFactor * (Integer) room.get(PRoom.SNAP_X));
if (show.contains(Show.GRID)) repaint(null);
break;
case SNAP_Y:
gridVisual.setHeight(gridFactor * (Integer) room.get(PRoom.SNAP_Y));
if (show.contains(Show.GRID)) repaint(null);
break;
case WIDTH:
case HEIGHT:
parent.updateBounds();
break;
}
}
}
private class BgDefPropertyListener extends PropertyUpdateListener<PBackgroundDef>
{
@Override
public void updated(PropertyUpdateEvent<PBackgroundDef> e)
{
boolean bg = show.contains(Show.BACKGROUNDS);
boolean fg = show.contains(Show.FOREGROUNDS);
if (!bg && !fg) return;
switch (e.key)
{
case FOREGROUND:
if (!(Boolean) e.map.get(PBackgroundDef.VISIBLE)) return;
case VISIBLE:
repaint(null);
case H_SPEED:
case V_SPEED:
return;
}
if (e.map.get(PBackgroundDef.VISIBLE))
if ((bg && fg) || (e.map.get(PBackgroundDef.FOREGROUND) ? fg : bg)) repaint(null);
}
}
public void updated(UpdateEvent e)
{
if (e.source.owner instanceof BackgroundDef)
{
boolean bg = show.contains(Show.BACKGROUNDS);
boolean fg = show.contains(Show.FOREGROUNDS);
if (!bg && !fg) return;
BackgroundDef bd = (BackgroundDef) e.source.owner;
if (bd.properties.get(PBackgroundDef.VISIBLE))
if ((bg && fg) || (bd.properties.get(PBackgroundDef.FOREGROUND) ? fg : bg)) repaint(null);
}
}
}